azcfg
Azure Confidential Field Gatherer - Set Azure Key Vault secrets to a struct
This module is used to get secrets from an Azure Key Vault and set them into a struct. The idea of parsing
configuration values into a struct was inspired by env
.
To mark a field in a struct to be populated by a secret set the struct tag secret
followed by the name
of the secret in Azure Key Vault, like so:
`secret:"<secret-name>"`
If the secret does not exist the field will keep the value it had prior to the call to Parse
.
The secret can be marked as required, this will make the call to Parse
return an error if the secret
does not exist:
secret:"<secret-name>,required"
The error message contains all fields that have been marked as required that didn't have a secret associated with them.
Note: Unexported fields will be ignored.
See example for more.
Getting started
Install
go get github.com/KarlGW/azcfg
Prerequisites
- Go 1.18
- Azure Key Vault
- Identity with access to secrets in the Key Vault
Authentication
The module supports several ways of authenticating to Azure and get secrets from the target Key Vault.
- Built-in credentials that supports Service Principal (Client Credentials with secret) and managed identity (system and user assigned)
- Credentials from
azidentity
with the submodule authopts
- Custom credential handling by implementing the
auth.Credential
interface.
For more information about option 2 and 3, see Credentials.
Built-in credentials
By default the module will attempt to determine credentials and target Key Vault with
environment variables.
Environment variables
Service Principal
AZCFG_KEYVAULT_NAME
- Name of the Azure Key Vault.
AZCFG_TENANT_ID
- Tenant ID of the service principal/application registration.
AZCFG_CLIENT_ID
- Client ID (also called Application ID) of the service principal/application registration.
AZCFG_CLIENT_SECRET
- Client Secret of the service principal/application registration.
Managed identity
AZCFG_KEYVAULT_NAME
- Name of the Azure Key Vault.
AZCFG_CLIENT_ID
- (Optional) Client ID (also called Application ID) of the Managed Identity. Set if using a user assigned managed identity.
Options
If more control is needed, such as custom environment variables or other means of getting the necessary values, options can be used.
Service Principal
azcfg.Parse(
&cfg,
azcfg.WithClientSecretCredential(tenantID, clientID, clientSecret),
WithVault(vault),
)
Managed identity
// System assigned identity.
azcfg.Parse(&cfg, WithManagedIdentity(), azcfg.WithVault(vault))
// User assigned identity.
azcfg.Parse(&cfg, WithManagedIdentity(clientID), azcfg.WithVault(vault))
To use a credential provided from elsewhere, such as the azidentity
module see the section about
Credentials.
Example
package main
import (
"github.com/KarlGW/azcfg"
)
type config struct {
Host string
Port int
Username string `secret:"username"`
Password string `secret:"password"`
Credential credential
}
type credential struct {
Key int `secret:"key"`
}
func main() {
cfg := config{}
if err := azcfg.Parse(&cfg); err != nil {
// Handle error.
}
fmt.Printf("%+v\n", cfg)
}
{Host: Port:0 Username:username-from-keyvault Password:password-from-keyvault Credential:{Key:12345}}
It is possible to pass options to Parse
:
package main
import (
"github.com/KarlGW/azcfg"
)
func main() {
cfg := config{}
if err := azcfg.Parse(&cfg, func(o *Options) {
o.Credential = cred
o.Vault = "vault"
o.Concurrenty = 20
o.Timeout = time.Millisecond * 1000 * 20
}); err != nil {
// Handle error.
}
}
An independent parser
can be created and passed around inside of the application.
package main
import (
"github.com/KarlGW/azcfg"
)
func main() {
parser, err := azcfg.NewParser()
if err != nil {
// Handle error.
}
cfg := config{}
if err := parser.Parse(&cfg); err != nil {
// Handle error.
}
}
The constructor function NewParser
supports the same options as the module level Parse
function.
For supported options see Options
struct or list of function options
Usage
Supported types
string
bool
uint
, uint8
, uint16
, uint32
, uint64
int
, int8
, int16
, int32
, int64
float32
, float64
Required
The default behaviour of Parse
is to ignore secrets that does not exist and let the field contain it's original value.
To enforce secrets to be set the option required
can be used.
type Example struct {
FieldA `secret:"field-a"`
FieldB `secret:"field-b,required"`
}
Options
Option functions are provided by the module for convenience:
WithConcurrency
WithTimeout
WithVault
WithClientSecretCredential
WithManagedIdentity
WithCredential
Credentials
Custom credentials with token retrieval can be used using the option WithCredential
. They must satisfy the interface Credential
:
// Credential is the interface that wraps around method Token.
type Credential interface {
Token(ctx context.Context) (Token, error)
}
Since it is reasonable to assume that credentials retrieved with the help of the azidentity
module might be used, a submodule, authopts
has been provided. This make it easer to reuse credentials from azidentity
.
Usage
go get github.com/KarlGW/azcfg/authopts
package main
import (
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
"github.com/KarlGW/azcfg"
"github.com/KarlGW/azcfg/authopts"
)
func main() {
cred, err := azidentity.NewDefaultAzureCredential(nil)
if err != nil {
// Handle error.
}
cfg := Config{}
if err := azcfg.Parse(&cfg, authopts.WithTokenCredential(cred)); err != nil {
// Handle error.
}
}
For additional information about how to use azidentity
, check its documentation.