Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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