Skip to content

Commit

Permalink
Merge 0e4db4f into b69be41
Browse files Browse the repository at this point in the history
  • Loading branch information
spbsoluble authored Nov 18, 2024
2 parents b69be41 + 0e4db4f commit 821bdd8
Show file tree
Hide file tree
Showing 9 changed files with 463 additions and 56 deletions.
17 changes: 16 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,4 +151,19 @@ servers:
client_id: client-id
client_secret: client-secret
api_path: KeyfactorAPI
```
```
## Configuration File Providers
Below are a list of configuration file providers that can be used to load configuration from a file if loading from disk
is not desired.
### Azure Key Vault
To use Azure Key Vault as a configuration file provider, the code must either be running in an Azure environment or the
environment configured with `az login`. The following environment variables will be used when they are not directly set.

| Name | Description | Default |
|---------------------|---------------------------------------|---------|
| AZURE_KEYVAULT_NAME | The name of the Azure KeyVault | |
| AZURE_SECRET_NAME | The name of the Azure KeyVault secret | |
10 changes: 5 additions & 5 deletions auth_providers/auth_basic.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,13 @@ type CommandAuthConfigBasic struct {
CommandAuthConfig

// Username is the username to be used for authentication to Keyfactor Command API
Username string `json:"username,omitempty"`
Username string `json:"username,omitempty" yaml:"username,omitempty"`

// Password is the password to be used for authentication to Keyfactor Command API
Password string `json:"password,omitempty"`
Password string `json:"password,omitempty" yaml:"password,omitempty"`

// Domain is the domain of the Active Directory used to authenticate to Keyfactor Command API
Domain string `json:"domain,omitempty"`
Domain string `json:"domain,omitempty" yaml:"domain,omitempty"`
}

// NewBasicAuthAuthenticatorBuilder creates a new instance of CommandAuthConfigBasic
Expand Down Expand Up @@ -194,8 +194,8 @@ func (a *CommandAuthConfigBasic) Authenticate() error {
return cErr
}

// create oauth Client
authy, err := NewBasicAuthAuthenticatorBuilder().
// create Basic Client
authy, err := a.
WithUsername(a.Username).
WithPassword(a.Password).
WithDomain(a.Domain).
Expand Down
24 changes: 13 additions & 11 deletions auth_providers/auth_core.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ func (f roundTripperFunc) RoundTrip(req *http.Request) (*http.Response, error) {
// CommandAuthConfig represents the base configuration needed for authentication to Keyfactor Command API.
type CommandAuthConfig struct {
// ConfigType is the type of configuration
ConfigType string `json:"config_type"`
ConfigType string `json:"config_type,omitempty" yaml:"config_type,omitempty"`

//ConfigProfile is the profile of the configuration
ConfigProfile string
Expand All @@ -110,34 +110,34 @@ type CommandAuthConfig struct {
FileConfig *Server

// AuthHeader is the header to be used for authentication to Keyfactor Command API
AuthHeader string `json:"auth_header"`
AuthHeader string `json:"auth_header,omitempty" yaml:"auth_header,omitempty"`

// CommandHostName is the hostname of the Keyfactor Command API
CommandHostName string `json:"host"`
CommandHostName string `json:"host,omitempty" yaml:"host,omitempty"`

// CommandPort is the port of the Keyfactor Command API
CommandPort int `json:"port"`
CommandPort int `json:"port,omitempty" yaml:"port,omitempty"`

// CommandAPIPath is the path of the Keyfactor Command API, default is "KeyfactorAPI"
CommandAPIPath string `json:"api_path"`
CommandAPIPath string `json:"api_path,omitempty" yaml:"api_path,omitempty"`

// CommandAPIVersion is the version of the Keyfactor Command API, default is "1"
CommandVersion string `json:"command_version"`
CommandVersion string `json:"command_version,omitempty" yaml:"command_version,omitempty"`

// CommandCACert is the CA certificate to be used for authentication to Keyfactor Command API for use with not widely trusted certificates. This can be a filepath or a string of the certificate in PEM format.
CommandCACert string `json:"command_ca_cert"`
CommandCACert string `json:"command_ca_cert,omitempty" yaml:"command_ca_cert,omitempty"`

// SkipVerify is a flag to skip verification of the server's certificate chain and host name. Default is false.
SkipVerify bool `json:"skip_verify"`
SkipVerify bool `json:"skip_verify,omitempty" yaml:"skip_verify,omitempty"`

// HttpClientTimeout is the timeout for the http Client
HttpClientTimeout int `json:"client_timeout"`
HttpClientTimeout int `json:"client_timeout,omitempty" yaml:"client_timeout,omitempty"`

// UserAgent is the user agent to be used for authentication to Keyfactor Command API
UserAgent string `json:"user_agent,omitempty"`
UserAgent string `json:"user_agent,omitempty" yaml:"user_agent,omitempty"`

// Debug
Debug bool `json:"debug,omitempty"`
Debug bool `json:"debug,omitempty" yaml:"debug,omitempty"`

// HttpClient is the http Client to be used for authentication to Keyfactor Command API
HttpClient *http.Client
Expand Down Expand Up @@ -731,6 +731,8 @@ func (c *CommandAuthConfig) GetServerConfig() *Server {
return &server
}

type contextKey string

// Example usage of CommandAuthConfig
//
// This example demonstrates how to use CommandAuthConfig to authenticate to the Keyfactor Command API.
Expand Down
27 changes: 11 additions & 16 deletions auth_providers/auth_oauth.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,40 +72,34 @@ type CommandConfigOauth struct {
CommandAuthConfig

// ClientID is the Client ID for OAuth authentication
ClientID string `json:"client_id,omitempty"`
ClientID string `json:"client_id,omitempty" yaml:"client_id,omitempty"`

// ClientSecret is the Client secret for OAuth authentication
ClientSecret string `json:"client_secret,omitempty"`
ClientSecret string `json:"client_secret,omitempty" yaml:"client_secret,omitempty"`

// Audience is the audience for OAuth authentication
Audience string `json:"audience,omitempty"`
Audience string `json:"audience,omitempty" yaml:"audience,omitempty"`

// Scopes is the scopes for OAuth authentication
Scopes []string `json:"scopes,omitempty"`
Scopes []string `json:"scopes,omitempty" yaml:"scopes,omitempty"`

// CACertificatePath is the path to the CA certificate for OAuth authentication
CACertificatePath string `json:"idp_ca_cert,omitempty"`
CACertificatePath string `json:"idp_ca_cert,omitempty" yaml:"idp_ca_cert,omitempty"`

// CACertificates is the CA certificates for authentication
CACertificates []*x509.Certificate `json:"-"`

// AccessToken is the access token for OAuth authentication
AccessToken string `json:"access_token,omitempty"`
AccessToken string `json:"access_token,omitempty" yaml:"access_token,omitempty"`

// RefreshToken is the refresh token for OAuth authentication
RefreshToken string `json:"refresh_token,omitempty"`
RefreshToken string `json:"refresh_token,omitempty" yaml:"refresh_token,omitempty"`

// Expiry is the expiry time of the access token
Expiry time.Time `json:"expiry,omitempty"`
Expiry time.Time `json:"expiry,omitempty" yaml:"expiry,omitempty"`

// TokenURL is the token URL for OAuth authentication
TokenURL string `json:"token_url,omitempty"`

//// AuthPort
//AuthPort string `json:"auth_port,omitempty"`

//// AuthType is the type of OAuth auth to use such as client_credentials, password, etc.
//AuthType string `json:"auth_type,omitempty"`
TokenURL string `json:"token_url,omitempty" yaml:"token_url,omitempty"`
}

// NewOAuthAuthenticatorBuilder creates a new CommandConfigOauth instance.
Expand Down Expand Up @@ -372,7 +366,7 @@ func (b *CommandConfigOauth) ValidateAuthConfig() error {
}
}

if b.Scopes == nil || len(b.Scopes) == 0 {
if len(b.Scopes) == 0 {
if scopes, ok := os.LookupEnv(EnvKeyfactorAuthScopes); ok {
// split the scopes by comma
b.Scopes = strings.Split(scopes, ",")
Expand Down Expand Up @@ -424,6 +418,7 @@ func (b *CommandConfigOauth) GetServerConfig() *Server {
Port: b.CommandPort,
ClientID: b.ClientID,
ClientSecret: b.ClientSecret,
AccessToken: b.AccessToken,
OAuthTokenUrl: b.TokenURL,
APIPath: b.CommandAPIPath,
Scopes: b.Scopes,
Expand Down
171 changes: 171 additions & 0 deletions auth_providers/azure_keyvault.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
package auth_providers

import (
"context"
"encoding/json"
"fmt"
"os"
"time"

"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
"github.com/Azure/azure-sdk-for-go/sdk/keyvault/azsecrets"
)

const (
EnvAzureVaultName = "AZURE_KEYVAULT_NAME"
EnvAzureSecretName = "AZURE_SECRET_NAME"
)

// ConfigProviderAzureKeyVault is an Authenticator that uses Azure Key Vault for authentication.
type ConfigProviderAzureKeyVault struct {
SecretName string `json:"secret_name,omitempty" yaml:"secret_name,omitempty"`
VaultName string `json:"vault_name,omitempty" yaml:"vault_name,omitempty"`
DefaultCredential *azidentity.DefaultAzureCredential
CommandConfig *Config
//TenantID string `json:"tenant_id;omitempty"`
//SubscriptionID string `json:"subscription_id;omitempty"`
//ResourceGroup string `json:"resource_group;omitempty"`
}

// NewConfigProviderAzureKeyVault creates a new instance of ConfigProviderAzureKeyVault.
func NewConfigProviderAzureKeyVault() *ConfigProviderAzureKeyVault {
return &ConfigProviderAzureKeyVault{}
}

// String returns a string representation of the ConfigProviderAzureKeyVault.
func (a *ConfigProviderAzureKeyVault) String() string {
return fmt.Sprintf("SecretName: %s, AzureVaultName: %s", a.SecretName, a.VaultName)
}

// WithSecretName sets the secret name for authentication.
func (a *ConfigProviderAzureKeyVault) WithSecretName(secretName string) *ConfigProviderAzureKeyVault {
a.SecretName = secretName
return a
}

// WithVaultName sets the vault name for authentication.
func (a *ConfigProviderAzureKeyVault) WithVaultName(vaultName string) *ConfigProviderAzureKeyVault {
a.VaultName = vaultName
return a
}

// Authenticate authenticates to Azure.
func (a *ConfigProviderAzureKeyVault) Authenticate() error {

vErr := a.Validate()
if vErr != nil {
return vErr
}

// Create a context with a timeout
ctx, cancel := context.WithTimeout(context.Background(), DefaultClientTimeout*time.Second)
defer cancel()

// Add custom metadata to context
ctx = context.WithValue(ctx, contextKey("operation"), "AzureAuthenticate")

// Try to authenticate using DefaultAzureCredential
cred, err := azidentity.NewDefaultAzureCredential(nil)
if err != nil {
return fmt.Errorf("failed to obtain a credential: %w", err)
}
a.DefaultCredential = cred
return nil
}

// Validate validates the ConfigProviderAzureKeyVault.
func (a *ConfigProviderAzureKeyVault) Validate() error {
if a.SecretName == "" {
// Check if the secret name is set in the environment
if secretName := os.Getenv(EnvAzureSecretName); secretName != "" {
a.SecretName = secretName
} else {
return fmt.Errorf("Azure KeyVault `SecretName` is required")
}
}
if a.VaultName == "" {
// Check if the vault name is set in the environment
if vaultName := os.Getenv(EnvAzureVaultName); vaultName != "" {
a.VaultName = vaultName
} else {
return fmt.Errorf("Azure KeyVault `VaultName` is required")
}
}
return nil
}

// LoadConfigFromAzureKeyVault loads a Config type from Azure Key Vault.
func (a *ConfigProviderAzureKeyVault) LoadConfigFromAzureKeyVault() (*Config, error) {
if a.DefaultCredential == nil {
aErr := a.Authenticate()
if aErr != nil {
return nil, aErr
}
}

// Create a context with a timeout
ctx, cancel := context.WithTimeout(context.Background(), DefaultClientTimeout*time.Second)
defer cancel()

// Add custom metadata to context
ctx = context.WithValue(ctx, contextKey("operation"), "LoadConfigFromAzureKeyVault")
ctx = context.WithValue(ctx, contextKey("vaultName"), a.VaultName)
ctx = context.WithValue(ctx, contextKey("secretName"), a.SecretName)
// Create a client to access the Azure Key Vault
vaultURL := fmt.Sprintf("https://%s.vault.azure.net/", a.VaultName)
client, cErr := azsecrets.NewClient(vaultURL, a.DefaultCredential, nil)
if cErr != nil {
return nil, cErr
}

// Retrieve the secret from the Azure Key Vault
secretResp, sErr := client.GetSecret(ctx, a.SecretName, "", nil)
if sErr != nil {
return nil, sErr
}

// Check if the secret value is nil to avoid dereferencing a nil pointer
if secretResp.Value == nil {
return nil, fmt.Errorf("secret value for '%s' in vault '%s' is nil", a.SecretName, a.VaultName)
}

// Parse the secret value into a Config type
var config Config
if jErr := json.Unmarshal([]byte(*secretResp.Value), &config); jErr != nil {
//attempt to unmarshal as a single server config
var singleServerConfig Server
if sjErr := json.Unmarshal([]byte(*secretResp.Value), &singleServerConfig); sjErr == nil {
config.Servers = make(map[string]Server)
config.Servers[DefaultConfigProfile] = singleServerConfig
} else {
return nil, jErr
}
}

a.CommandConfig = &config
return &config, nil
}

// Example usage of ConfigProviderAzureKeyVault
//
// This example demonstrates how to use ConfigProviderAzureKeyVault to load a configuration from Azure Key Vault.
//
// func ExampleConfigProviderAzureKeyVault() {
// provider := NewConfigProviderAzureKeyVault().
// WithSecretName("my-secret").
// WithVaultName("my-vault")
//
// err := provider.Authenticate()
// if err != nil {
// fmt.Println("Failed to authenticate:", err)
// return
// }
//
// config, err := provider.LoadConfigFromAzureKeyVault()
// if err != nil {
// fmt.Println("Failed to load config from Azure Key Vault:", err)
// return
// }
//
// fmt.Println("Loaded config from Azure Key Vault:", config)
// }
Loading

0 comments on commit 821bdd8

Please sign in to comment.