Skip to content
Merged
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
1 change: 1 addition & 0 deletions charts/ratify/templates/store.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ spec:
authProvider:
name: k8Secrets
serviceAccountName: {{ include "ratify.serviceAccountName" . }}
secretTimeout: {{ .Values.oras.authProviders.k8secretsSecretTimeout }}
{{- end }}
{{- if .Values.oras.authProviders.awsEcrBasicEnabled }}
authProvider:
Expand Down
1 change: 1 addition & 0 deletions charts/ratify/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ oras:
azureManagedIdentityEnabled: false
azureContainerRegistryEndpoints: []
k8secretsEnabled: false
k8secretsSecretTimeout: 43200
awsEcrBasicEnabled: false
awsApiOverride:
enabled: false
Expand Down
5 changes: 4 additions & 1 deletion docs/design/K8s Secrets AuthProvider.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,8 @@ roleRef:
"secretName": "test2.ghcr.io",
"namespace": "test"
}
]
],
"secretTimeout": 43200 // in seconds - 43200s or 12h used by default if not specified
}
}
]
Expand Down Expand Up @@ -106,6 +107,7 @@ type k8SecretAuthProviderConf struct {
Name string `json:"name"`
ServiceAccountName string `json:"serviceAccountName,omitempty"`
Secrets []secretConfig `json:"secrets,omitempty"`
SecretTimeout uint32 `json:"secretTimeout,omitempty"`
Comment thread
fseldow marked this conversation as resolved.
}

func init() // init calls Register for our k8s-secrets provider
Expand Down Expand Up @@ -141,6 +143,7 @@ func (d *k8SecretAuthProvider) Provide(artifact string) (AuthConfig, error) {
// if .dockercfg secret, deserialize .dockercfg data, see if matching registry host credential exists, extract auth configs and return matching AuthConfig if it exists
// if config.json secret do same as above except using non legacy format

// set the cache TTL to the "SecretTimeout" property (defaults to 12h)
}

```
Expand Down
36 changes: 27 additions & 9 deletions pkg/common/oras/authprovider/k8secret_authprovider.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,11 @@ type k8SecretAuthProviderConf struct {
Name string `json:"name"`
ServiceAccountName string `json:"serviceAccountName,omitempty"`
Secrets []secretConfig `json:"secrets,omitempty"`
SecretTimeout *uint32 `json:"secretTimeout,omitempty"`
}

const defaultName = "default"
const secretTimeout = time.Hour * 12
const defaultSecretTimeout = 3600 * 12 * time.Second

// init calls Register for our k8Secrets provider
func init() {
Expand All @@ -65,14 +66,9 @@ func init() {
// Create returns a k8AuthProvider instance after parsing auth config and resolving
// named K8s secrets
func (s *k8SecretProviderFactory) Create(authProviderConfig AuthProviderConfig) (AuthProvider, error) {
conf := k8SecretAuthProviderConf{}
authProviderConfigBytes, err := json.Marshal(authProviderConfig)
conf, err := parseAuthProviderConfig(authProviderConfig)
if err != nil {
return nil, re.ErrorCodeConfigInvalid.NewError(re.AuthProvider, "", re.EmptyLink, err, "failed to marshal authentication provider config", re.HideStackTrace)
}

if err := json.Unmarshal(authProviderConfigBytes, &conf); err != nil {
return nil, re.ErrorCodeConfigInvalid.NewError(re.AuthProvider, "", re.EmptyLink, err, "failed to parse authentication provider configuration", re.HideStackTrace)
return nil, re.ErrorCodeConfigInvalid.NewError(re.AuthProvider, "", re.EmptyLink, err, "failed to deserialize auth provider config", re.HideStackTrace)
}

clusterConfig, err := rest.InClusterConfig()
Expand Down Expand Up @@ -102,6 +98,20 @@ func (s *k8SecretProviderFactory) Create(authProviderConfig AuthProviderConfig)
}, nil
}

func parseAuthProviderConfig(authProviderConfig AuthProviderConfig) (k8SecretAuthProviderConf, error) {
conf := k8SecretAuthProviderConf{}
authProviderConfigBytes, err := json.Marshal(authProviderConfig)
if err != nil {
return conf, re.ErrorCodeConfigInvalid.NewError(re.AuthProvider, "", re.EmptyLink, err, "failed to marshal authentication provider config", re.HideStackTrace)
}

if err := json.Unmarshal(authProviderConfigBytes, &conf); err != nil {
return conf, re.ErrorCodeConfigInvalid.NewError(re.AuthProvider, "", re.EmptyLink, err, "failed to parse authentication provider configuration", re.HideStackTrace)
}

return conf, nil
}

// Enabled checks if ratify namespace, config, or cluster client set is nil
func (d *k8SecretAuthProvider) Enabled(_ context.Context) bool {
if d.ratifyNamespace == "" || d.clusterClientSet == nil {
Expand Down Expand Up @@ -215,6 +225,14 @@ func (d *k8SecretAuthProvider) resolveCredentialFromSecret(ctx context.Context,
Password: authConfig.Password,
IdentityToken: authConfig.IdentityToken,
Provider: d,
ExpiresOn: time.Now().Add(secretTimeout),
ExpiresOn: time.Now().Add(d.getSecretTimeout()),
}, nil
}

func (d *k8SecretAuthProvider) getSecretTimeout() time.Duration {
if d.config.SecretTimeout == nil {
return defaultSecretTimeout
}

return time.Second * time.Duration(*d.config.SecretTimeout)
}
59 changes: 58 additions & 1 deletion pkg/common/oras/authprovider/k8secret_authprovider_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"context"
"errors"
"testing"
"time"

ratifyerrors "github.com/ratify-project/ratify/errors"
core "k8s.io/api/core/v1"
Expand Down Expand Up @@ -329,6 +330,7 @@ func TestProvider_SecretFound_ReturnsSuccess(t *testing.T) {
// TestProvide_ServiceAccountSecretFound_ReturnsSuccess tests that the Provide method
// returns auth config when a matching credential is found for a service account image pull secret
func TestProvider_ServiceAccountSecretFound_ReturnsSuccess(t *testing.T) {
secretTimeout := uint32(7200)
k8secretprovider := k8SecretAuthProvider{
ratifyNamespace: "gatekeeper-system",
clusterClientSet: fake.NewSimpleClientset(&core.ServiceAccount{
Expand All @@ -353,10 +355,65 @@ func TestProvider_ServiceAccountSecretFound_ReturnsSuccess(t *testing.T) {
}),
config: k8SecretAuthProviderConf{
ServiceAccountName: "ratify-admin",
SecretTimeout: &secretTimeout,
},
}

if _, err := k8secretprovider.Provide(context.Background(), "index.docker.io/artifact:v1"); err != nil {
if k8secretprovider.getSecretTimeout() != 7200*time.Second {
t.Fatalf("time passed in config but could not verify the correct secret expiration time")
}

authConfig, err := k8secretprovider.Provide(context.Background(), "index.docker.io/artifact:v1")
if err != nil {
t.Fatalf("Provide failed to get credential with err %v", err)
}

timeDifference := time.Now().Add(time.Duration(secretTimeout) * time.Second).Sub(authConfig.ExpiresOn).Abs()
epsilon := 5 * time.Second
if timeDifference > epsilon { // using 5 seconds as epsilon value for comparison to account for execution delays
t.Fatalf("auth config ExpiresOn is not set properly - expected to be set to 2 hours from current time: %v", err)
}
}

func TestProvider_NilSecretTimeoutReturnsDefault(t *testing.T) {
authConfig := AuthProviderConfig{
"name": "k8Secrets",
"serviceAccountName": "ratify-admin",
}
parsedAuthConf, err := parseAuthProviderConfig(authConfig)
if err != nil {
t.Fatalf("could not parse auth config properly: %v", err)
}

if parsedAuthConf.SecretTimeout != nil {
t.Fatalf("expected SecretTimeout to be nil due to no secretTimeout config in auth provider config")
}

authProvider := &k8SecretAuthProvider{
config: parsedAuthConf,
}
if authProvider.getSecretTimeout() != defaultSecretTimeout {
t.Fatalf("expected secret timeout to be defaulted to 12h due to nil secret timeout in auth config")
}
}

func TestProvider_Deserialization_Success(t *testing.T) {
authProviderConfig := AuthProviderConfig{
"name": "k8Secrets",
"serviceAccountName": "ratify-admin",
"secretTimeout": 7200,
}

conf, err := parseAuthProviderConfig(authProviderConfig)
if err != nil {
t.Fatalf("Unable to parse auth provider config: %v", err)
}

if conf.Name != "k8Secrets" ||
conf.ServiceAccountName != "ratify-admin" ||
conf.SecretTimeout == nil ||
*conf.SecretTimeout != 7200 ||
conf.Secrets != nil {
t.Fatalf("Parsed auth config does not match the input: %v", err)
}
}
2 changes: 1 addition & 1 deletion pkg/controllers/clusterresource/store_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,7 @@ func resetStoreMap() {
}

func getOrasStoreSpec(pluginName, pluginPath string) configv1beta1.StoreSpec {
var parametersString = "{\"authProvider\":{\"name\":\"k8Secrets\",\"secrets\":[{\"secretName\":\"myregistrykey\"}]},\"cosignEnabled\":false,\"useHttp\":false}"
var parametersString = "{\"authProvider\":{\"name\":\"k8Secrets\",\"secrets\":[{\"secretName\":\"myregistrykey\"}],\"secretTimeout\":3600},\"cosignEnabled\":false,\"useHttp\":false}"
var storeParameters = []byte(parametersString)

return configv1beta1.StoreSpec{
Expand Down
Loading