Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion deployment/chainloop/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ description: Chainloop is an open source software supply chain control plane, a

type: application
# Bump the patch (not minor, not major) version on each change in the Chart Source code
version: 1.451.0
version: 1.451.1
# Do not update appVersion, this is handled automatically by the release process
appVersion: v1.113.0

Expand Down
15 changes: 13 additions & 2 deletions deployment/chainloop/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,16 @@ secretsBackend:
region: [REGION]
```

To authenticate without stored keys (EKS Pod Identity, IRSA or an instance role), select the AWS SDK default credential chain explicitly and leave the keys unset

```yaml
secretsBackend:
backend: awsSecretManager
awsSecretManager:
authType: AUTH_TYPE_AMBIENT
region: [REGION]
```

### Use GCP secret manager

Or [Google Cloud Secret Manager](https://cloud.google.com/secret-manager) with the following settings
Expand Down Expand Up @@ -540,8 +550,9 @@ Once done, you can access with [two predefined users](https://github.com/chainlo
| `secretsBackend.secretPrefix` | Prefix that will be pre-pended to all secrets in the storage backend | `chainloop` |
| `secretsBackend.vault.address` | Vault address | |
| `secretsBackend.vault.token` | Vault authentication token | |
| `secretsBackend.awsSecretManager.accessKey` | AWS Access KEY ID | |
| `secretsBackend.awsSecretManager.secretKey` | AWS Secret Key | |
| `secretsBackend.awsSecretManager.authType` | AUTH_TYPE_CREDENTIALS (default, static keys) or AUTH_TYPE_AMBIENT (default chain) | |
| `secretsBackend.awsSecretManager.accessKey` | AWS Access KEY ID (AUTH_TYPE_CREDENTIALS only) | |
| `secretsBackend.awsSecretManager.secretKey` | AWS Secret Key (AUTH_TYPE_CREDENTIALS only) | |
| `secretsBackend.awsSecretManager.region` | AWS Secrets Manager Region | |
| `secretsBackend.gcpSecretManager.projectId` | GCP Project ID | |
| `secretsBackend.gcpSecretManager.serviceAccountKey` | GCP Auth Key | |
Expand Down
10 changes: 10 additions & 0 deletions deployment/chainloop/templates/_helpers.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -81,11 +81,21 @@ vault:
{{- end }}

{{- else if eq .backend "awsSecretManager" }}
{{- $authType := .awsSecretManager.authType | default "AUTH_TYPE_CREDENTIALS" }}
awsSecretManager:
region: {{ required "region required" .awsSecretManager.region | quote }}
authType: {{ $authType | quote }}
{{- if eq $authType "AUTH_TYPE_CREDENTIALS" }}
creds:
accessKey: {{ required "access key required" .awsSecretManager.accessKey | quote }}
secretKey: {{ required "secret key required" .awsSecretManager.secretKey | quote }}
{{- else if eq $authType "AUTH_TYPE_AMBIENT" }}
{{- if or .awsSecretManager.accessKey .awsSecretManager.secretKey }}
{{- fail "secretsBackend.awsSecretManager: accessKey and secretKey must not be set with authType AUTH_TYPE_AMBIENT" }}
{{- end }}
{{- else }}
{{- fail (printf "secretsBackend.awsSecretManager.authType %q is not one of AUTH_TYPE_CREDENTIALS, AUTH_TYPE_AMBIENT" $authType) }}
{{- end }}

{{- else if eq .backend "gcpSecretManager" }}
gcpSecretManager:
Expand Down
6 changes: 4 additions & 2 deletions deployment/chainloop/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -70,11 +70,13 @@ secretsBackend:
# address: ""
# token: ""

## @extra secretsBackend.awsSecretManager.accessKey AWS Access KEY ID
## @extra secretsBackend.awsSecretManager.secretKey AWS Secret Key
## @extra secretsBackend.awsSecretManager.authType AUTH_TYPE_CREDENTIALS (default, static keys) or AUTH_TYPE_AMBIENT (default chain)
## @extra secretsBackend.awsSecretManager.accessKey AWS Access KEY ID (AUTH_TYPE_CREDENTIALS only)
## @extra secretsBackend.awsSecretManager.secretKey AWS Secret Key (AUTH_TYPE_CREDENTIALS only)
## @extra secretsBackend.awsSecretManager.region AWS Secrets Manager Region
##
# awsSecretManager:
# authType: AUTH_TYPE_CREDENTIALS
# accessKey: ""
# secretKey: ""
# region: ""
Expand Down
122 changes: 96 additions & 26 deletions pkg/credentials/api/credentials/v1/config.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 13 additions & 1 deletion pkg/credentials/api/credentials/v1/config.proto
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,21 @@ message Credentials {

// Top level is deprecated now
message AWSSecretManager {
Creds creds = 1 [(buf.validate.field).required = true];
// Required for AUTH_TYPE_CREDENTIALS, rejected for AUTH_TYPE_AMBIENT.
Creds creds = 1;
Comment thread
khrisrichardson marked this conversation as resolved.
string region = 2 [(buf.validate.field).string.min_len = 1];

enum AuthType {
AUTH_TYPE_UNSPECIFIED = 0;
// Use the static keys in creds.
AUTH_TYPE_CREDENTIALS = 1;
// Use the AWS SDK default credential chain (EKS Pod Identity, IRSA, instance role).
AUTH_TYPE_AMBIENT = 2;
}

// How to authenticate. AUTH_TYPE_UNSPECIFIED is treated as AUTH_TYPE_CREDENTIALS.
AuthType auth_type = 3 [(buf.validate.field).enum.defined_only = true];

message Creds {
string access_key = 1 [(buf.validate.field).string.min_len = 1];
string secret_key = 2 [(buf.validate.field).string.min_len = 1];
Expand Down
57 changes: 48 additions & 9 deletions pkg/credentials/aws/secretmanager.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"strings"

"github.com/aws/aws-sdk-go-v2/aws"
awsconfig "github.com/aws/aws-sdk-go-v2/config"
awscreds "github.com/aws/aws-sdk-go-v2/credentials"
"github.com/aws/aws-sdk-go-v2/service/secretsmanager"
smtypes "github.com/aws/aws-sdk-go-v2/service/secretsmanager/types"
Expand All @@ -49,15 +50,41 @@ type Manager struct {
logger *log.Helper
}

// AuthType selects how the manager authenticates to AWS. The zero value is AuthTypeCredentials, so a configuration
// that does not choose keeps using static keys.
type AuthType int

const (
// AuthTypeCredentials uses the static AccessKey and SecretKey.
AuthTypeCredentials AuthType = iota
// AuthTypeAmbient uses the AWS SDK default credential chain (EKS Pod Identity, IRSA, instance role).
AuthTypeAmbient
)

type NewManagerOpts struct {
Region, AccessKey, SecretKey, SecretPrefix string
AuthType AuthType
Logger log.Logger
Role credentials.Role
}

func NewManager(opts *NewManagerOpts) (*Manager, error) {
if opts.Region == "" || opts.AccessKey == "" || opts.SecretKey == "" {
return nil, errors.New("region, accessKey and the secretKey are required")
if opts.Region == "" {
return nil, errors.New("region is required")
}
// The operator chooses ambient authentication explicitly, so forgotten keys fail here instead of silently
// falling through to whatever the default chain finds (for example the node's instance role).
switch opts.AuthType {
case AuthTypeCredentials:
if opts.AccessKey == "" || opts.SecretKey == "" {
return nil, errors.New("accessKey and secretKey are required for the credentials auth type")
}
case AuthTypeAmbient:
if opts.AccessKey != "" || opts.SecretKey != "" {
return nil, errors.New("accessKey and secretKey must not be set for the ambient auth type")
}
default:
return nil, fmt.Errorf("unknown auth type %d", opts.AuthType)
}

l := opts.Logger
Expand All @@ -66,21 +93,33 @@ func NewManager(opts *NewManagerOpts) (*Manager, error) {
}

logger := servicelogger.ScopedHelper(l, "credentials/aws-secrets-manager")
logger.Infow("msg", "configuring secrets-manager", "region", opts.Region, "role", opts.Role, "prefix", opts.SecretPrefix)
logger.Infow("msg", "configuring secrets-manager", "region", opts.Region, "role", opts.Role, "prefix", opts.SecretPrefix, "ambient", opts.AuthType == AuthTypeAmbient)

// Using AWS config directly instead of using config.LoadDefaultConfig
// to avoid the default credential chain and use only the static credentials
config := aws.Config{
Region: opts.Region,
Credentials: awscreds.NewStaticCredentialsProvider(opts.AccessKey, opts.SecretKey, ""),
cfg, err := loadConfig(opts)
if err != nil {
return nil, fmt.Errorf("loading AWS configuration: %w", err)
}

return &Manager{
client: secretsmanager.NewFromConfig(config),
client: secretsmanager.NewFromConfig(cfg),
secretPrefix: opts.SecretPrefix, logger: logger,
}, nil
}

// loadConfig uses only the static keys for AuthTypeCredentials, never falling through to ambient credentials. For
// AuthTypeAmbient it resolves credentials through the SDK's default chain, so no long-lived key has to be stored for
// the control plane or CAS.
func loadConfig(opts *NewManagerOpts) (aws.Config, error) {
if opts.AuthType == AuthTypeCredentials {
return aws.Config{
Region: opts.Region,
Credentials: awscreds.NewStaticCredentialsProvider(opts.AccessKey, opts.SecretKey, ""),
}, nil
}

return awsconfig.LoadDefaultConfig(context.Background(), awsconfig.WithRegion(opts.Region))
}

// SaveCredentials saves credentials. If opts includes WithExistingSecret, upserts at the given path.
func (m *Manager) SaveCredentials(ctx context.Context, orgID string, creds any, opts ...credentials.SaveOption) (string, error) {
o := credentials.ApplySaveOptions(opts...)
Expand Down
Loading