Skip to content

Commit

Permalink
docs: add design docs (#1136)
Browse files Browse the repository at this point in the history
  • Loading branch information
akashsinghal authored Oct 24, 2023
1 parent 0943c02 commit 14ebeb3
Show file tree
Hide file tree
Showing 16 changed files with 1,975 additions and 0 deletions.
24 changes: 24 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -1 +1,25 @@
Please see the [Ratify website](https://ratify.dev/docs/1.0/what-is-ratify) for more in-depth information.

## Design Docs

List of implemented design and discussion documents. Please open a PR to update this README and add a proposed design doc.

### Implemented

* [Load Testing Pipeline](design/Load%20Testing%20Pipeline.md)
* [TLS Certificate Rotation](design/TLS%20Certificate%20Rotation.md)
* [Registry Credential Caching](design/Registry%20Credential%20Caching.md)
* [Cache Unification](design/Cache%20Unification.md)
* [Tag to Digest Mutation](design/Tag%20to%20Digest%20Mutation.md)
* [Metrics](design/Metrics.md)
* [Concurrency](design/Concurrency.md)
* [Cosign Refactor](design/Cosign%20Refactor.md)
* [Policy Provider refactor (deprecated)](design/Policy%20Provider%20refactor%20(deprecated).md)
* [Azure Kubernetes Workload Identity Support](design/Azure%20Kubernetes%20Workload%20Identity%20AuthProvider.md)
* [K8s Authentication Provider](design/K8s%20Secrets%20AuthProvider.md)
* [Authentication Provider Framework](design/Authentication%20Provider%20Support%20For%20ORAS%20Store.md)
### Discussion

* [Gatekeeper Timeout Constraint](discussion/Gatekeeper%20Timeout%20Constraint.md)
* [Image Platform Selection](discussion/Image%20Platform%20Selection.md)
* [Negative Test Cases v1.0.0](discussion/Negative%20test%20cases%20for%20Ratify.md)
190 changes: 190 additions & 0 deletions docs/design/Authentication Provider Support For ORAS Store.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
# Add Authentication Provider Support For ORAS Store

Author: Akash Singhal (@akashsinghal)

General Design Document for [Ratify Auth](https://hackmd.io/LFWPWM7wT_icfIPZbuax0Q#Auth-using-metadata-service-endpoint-in-k8s)

Linked PR: https://github.com/deislabs/ratify/pull/123


## Goals
1. Add AuthProvider extensible interface with AuthConfig spec mirroring Docker AuthConfig. Used only for ORAS store.
2. Modify ORAS Store to consume AuthConfig for authentication. AuthConfig provided by AuthProvider specificied in config for ORAS plugin

Each Referrer store is likely to have different authentication requirements (e.g. a SQL DB referrer store doesn't use Docker Auth and instead relies on secure connection strings). Therefore, the Authentication Provider interface will be specific to each referrer store. In this case the `AuthProvider` will be specific to ORAS.

Currently, ORAS is undergoing a major refactor which will culminate in a v2 release that alters the way credentials are handled by a remote registry. Prior to that release, we must rely upon traditional `AuthConfig` to provide credentials to registry. Once we decide to transition to the v2 API, the ratify refactor will include switching to using the ORAS [Credential object](https://github.com/oras-project/oras-go/blob/6dfff48efc4fd0c3b13ae202d505093426137554/registry/remote/auth/credential.go#L22). Since we don't want to specifically block on a stable v2 version release for ORAS we will rely upon `AuthConfig` in the mean time. The switch over should not be that involved.

## ORAS Store Authentication Interfaces

### Sample Ratify Config File

```
{
"stores": {
"version": "1.0.0",
"plugins": [
{
"name": "oras",
"localCachePath": "./local_oras_cache",
"auth-provider": {
"name": "<auth provider name>",
<other provider specific fields>
}
}
]
},
"verifiers": {
"version": "1.0.0",
"plugins": [
{
"name":"notaryv2",
"artifactTypes" : "application/vnd.cncf.notary.v2.signature",
"verificationCerts": [
"<cert folder>"
]
}
]
}
}
```
### AuthProvider Interface

Based on [DockerConfigProvider](https://github.com/kubernetes/kubernetes/blob/2cd8ceb2694ef30d93cccb53445e9add6cbd9f7f/pkg/credentialprovider/provider.go#L30)

```
type AuthProvider interface {
// Enabled returns true if the config provider is properly enabled
// It will verify necessary values provided in config file to
// create the AuthProvider
Enabled() bool
// Provide returns AuthConfig for registry.
Provide(artifact string) (AuthConfig, error)
}
```

### AuthConfig Object

Based on [DockerConfig](https://github.com/kubernetes/kubernetes/blob/2cd8ceb2694ef30d93cccb53445e9add6cbd9f7f/pkg/credentialprovider/config.go#L50)

```
// This config that represents the credentials that should be used
// when pulling artifacts from specific repositories.
type AuthConfig struct {
Username string
Password string
Provider AuthProvider
// Add more fields if necessary such as access tokens
}
```

### Files Added
- Create AuthProvider file with default docker config provider implementation like [K8s](https://github.com/kubernetes/kubernetes/blob/2cd8ceb2694ef30d93cccb53445e9add6cbd9f7f/pkg/credentialprovider/provider.go).

```
type AuthProvider interface {
// Enabled returns true if the config provider is properly enabled
// It will verify necessary values provided in config file to
// create the AuthProvider
Enabled() bool
// Provide returns AuthConfig for registry.
Provide(artifact string) (AuthConfig, error)
}
type defaultProviderFactory struct{}
type defaultAuthProvider struct{}
// init calls Register for our default provider, which simply reads the .dockercfg file.
func init() {
AuthProviderFactory.Register("default", &defaultProviderFactory)
}
// Create defaultAuthProvider
func (s *defaultProviderFactory) Create(authProviderConfig AuthProviderConfig) (AuthProvider, error)
// Enabled implements AuthProvider; Always returns true for the default provider
func (d *defaultAuthProvider) Enabled() bool
// Provide implements AuthProvider; reads docker config file and returns corresponding credentials from file if exists
func (d *defaultAuthProvider) Provide(artifact string) (AuthConfig, error)
```
- Create AuthProviderFactory which will register new AuthProviderFactory for a new AuthProvider and return the correct AuthProvider given the AuthProviderConfig
```
var builtInAuthProviders = make(map[string]AuthProviderFactory)
// AuthProviderFactory is an interface that defines methods to create an AuthProvider
type AuthProviderFactory interface {
Create(authProviderConfig AuthProviderConfig) (AuthProvider, error)
}
// Add the factory to the built in providers map
func Register(name string, factory AuthProviderFactory)
// CreateAuthProviderFromConfig creates AuthProvider from the provided configuration
// If the AuthProviderConfig isn't specified, use default auth provider
func CreateAuthProviderFromConfig(authProviderConfig AuthProviderConfig) (AuthProvider, error)
func validateAuthProviderConfig(authProviderConfig AuthProviderConfig) error
```
- AuthProviderConfig definition is simply a map to the provided AuthProviderConfig

```
// AuthProviderConfig represents the configuration of an AuthProvider
type AuthProviderConfig map[string]interface{}
```

## ORAS Store Modification

### Changes to Accept New AuthProvider Config

Add AuthProviderConfig object in OrasConfig:
```
type OrasStoreConf struct {
Name string `json:"name"`
UseHttp bool `json:"useHttp,omitempty"`
CosignEnabled bool `json:"cosign-enabled,omitempty"`
AuthProvider AuthProviderConfig `json:"auth-provider,omitempty"`
LocalCachePath String `json:"localCachePath,omitempty"`
}
```

Update `Create` [method](https://github.com/deislabs/ratify/blob/6edd4ceedc21cf704857eae56b2197e0e28f0f93/pkg/referrerstore/oras/oras.go#L68) in oras.go

```
func (s *orasStoreFactory) Create(version string, storeConfig config.StorePluginConfig) (referrerstore.ReferrerStore, error) {
...
// Call the AuthProviderFactory CreateAuthProvidersFromConfig method with the AuthProviderConfig parameter
...
}
```

### Changes to use new AuthProvider

```
func (store *orasStore) createRegistryClient(targetRef common.Reference) (*content.Registry, error) {
...
// Use AuthProvider's Provide method to get repository's AuthConfig
// Specify the Username and Password in the RegistryOptions
...
return content.NewRegistryWithDiscover(targetRef.Original, registryOpts)
}
```
**NOTE: We need to migrate ORAS store to new ORAS-go v2 library

# Questions

1. For each store, should we support multiple auth providers or just one?
- No. We currently can't think of a scenario where multiple AuthProviders would be needed for a single store
2. ORAS store relies upon the artifacts implementation of ORAS in v1 while the new ORAS auth is in v2. Do we stick with current version of ORAS and build AuthCredentials integration with ORAS v1?
- We will eventually migrate to the v2 oras-go library once released. For now all implementations will work with v1

108 changes: 108 additions & 0 deletions docs/design/Azure Kubernetes Workload Identity AuthProvider.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
# Azure Kubernetes Workload Identity AuthProvider
Author: Akash Singhal (@akashsinghal)

Pod Identity will be deprecated in the future and the industry standard moving forward for accessing cloud services from an application is [Workload Identity](https://azure.github.io/azure-workload-identity/docs/introduction.html).

User would need to follow instructions [here](https://azure.github.io/azure-workload-identity/docs/quick-start.html) to enable workload identity for their cluster. To access a specific ACR, the AAD Application created for Workload Identity would need to be assigned an ACRpull role to the ACR.

## High Level Auth Flow using Workload Identity

1. The Kubernetes Service Account, which is provided federated identity, injects the tenant_id, authority_host, client_id, and token_file path as environment variables in the pod. The token file is also mounted at the token_file path.
2. Token file is used to generate a confidential client (msal-go library)
3. Confidential Client is used to request an AAD token with the `https://containerregistry.azure.com/` scope. This returns a valid containerregistry scoped access token.
4. Artifact login server is challenged and a directive is returned for token exchange.
5. ARM token is exchanged for a registry refresh token
6. Registry username is simply the default docker GUID (`00000000-0000-0000-0000-000000000000`) and the password is the registry refresh token


## User steps to setup an AKS cluster and delegate access to specific private ACR
The official steps for setting up Workload Identity on AKS can be found [here](https://azure.github.io/azure-workload-identity/docs/quick-start.html).

1. Create ACR
2. Create OIDC enabled AKS cluster by follow steps [here](https://docs.microsoft.com/en-us/azure/aks/cluster-configuration#oidc-issuer-preview)
3. Save the cluster's OIDC URL: `az aks show --resource-group <resource_group> --name <cluster_name> --query "oidcIssuerProfile.issuerUrl" -otsv`
4. Install Mutating Admission Webhook onto AKS cluster by following steps [here](https://azure.github.io/azure-workload-identity/docs/installation/mutating-admission-webhook.html)
5. As the guide linked above shows, it's possible to use the AZ workload identity CLI or the regular az CLI to perform remaining setup. Following steps follow the AZ CLI.
6. Create ACR AAD application: `az ad sp create-for-rbac --name "<APPLICATION_NAME>"`
7. On Portal or AZ CLI, enable acrpull role to the AAD application for the ACR resource
8. Use kubectl to add service account to cluster:
```
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: ServiceAccount
metadata:
annotations:
azure.workload.identity/client-id: ${APPLICATION_CLIENT_ID}
labels:
azure.workload.identity/use: "true"
name: ${SERVICE_ACCOUNT_NAME}
namespace: ${SERVICE_ACCOUNT_NAMESPACE}
EOF
```
9. From Azure Cloud Shell:
```
export APPLICATION_OBJECT_ID="$(az ad app show --id ${APPLICATION_CLIENT_ID} --query objectId -otsv)"
cat <<EOF > body.json
{
"name": "kubernetes-federated-credential",
"issuer": "${SERVICE_ACCOUNT_ISSUER}",
"subject": "system:serviceaccount:${SERVICE_ACCOUNT_NAMESPACE}:${SERVICE_ACCOUNT_NAME}",
"description": "Kubernetes service account federated credential",
"audiences": [
"api://AzureADTokenExchange"
]
}
EOF
az rest --method POST --uri "https://graph.microsoft.com/beta/applications/${APPLICATION_OBJECT_ID}/federatedIdentityCredentials" --body @body.json
```
10. In the Pod spec, add the `serviceAccountName` property. Example Pod spec:
```
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: quick-start
namespace: <SERVICE_ACCOUNT_NAMESPACE>
spec:
serviceAccountName: <SERVICE_ACCOUNT_NAME>
containers:
- image: ghcr.io/azure/azure-workload-identity/msal-go:latest
name: oidc
env:
- name: KEYVAULT_NAME
value: ${KEYVAULT_NAME}
- name: SECRET_NAME
value: ${KEYVAULT_SECRET_NAME}
nodeSelector:
kubernetes.io/os: linux
EOF
```

## AzureWIAuthProvider Implementation
```
// AzureK8Conf describes the configuration of Azure K8 Auth Provider
type AzureWIConf struct {
Name string `json:"name"`
}
type AzureWIAuthProviderFactory struct{}
type AzureWIAuthProvider struct {}
// init calls Register for our Azure WI provider
func init() {
AuthProviderFactory.Register("azure-wi", &AzureWIAuthProviderFactory)
}
// create returns the AzureK8AuthProvider
func (s *AzureWIAuthProviderFactory) Create(authProviderConfig AuthProviderConfig) (AuthProvider, error)
// Enabled implements AuthProvider; Can be used to verify all fields for WI exist
func (d *AzureK8AuthProvider) Enabled() bool
// Provide implements AuthProvider; follows auth flow outlined above
func (d *AzureWIAuthProvider) Provide(artifact string) (AuthConfig, error)
```
## Questions
1. The Gatekeeper timeout of 3 seconds leads to a premature timeout of the deployment. The authentication requires many roundtrips. Currently, a basic registry client cache is implemented such that a retry of the deployment will succeed in the next attempt. Is there a way to increase Gatekeeper timeout? Or frontload Auth before the webhook starts?
Loading

0 comments on commit 14ebeb3

Please sign in to comment.