Skip to content

Commit

Permalink
Add Azure Container Registry support (#82)
Browse files Browse the repository at this point in the history
* Add Azure Container Registry support

Includes
- updates to README.md
- some test streamlining
- updated testify/assert test dependency to latest release

* Revert tag change
  • Loading branch information
karolz-ms authored and stevesloka committed Jan 21, 2020
1 parent 9aa1eb2 commit 873632a
Show file tree
Hide file tree
Showing 19 changed files with 3,777 additions and 673 deletions.
3 changes: 2 additions & 1 deletion .gitignore
@@ -1 +1,2 @@
registry-creds
registry-creds
.vscode/
29 changes: 28 additions & 1 deletion README.md
Expand Up @@ -5,7 +5,7 @@ Allow for Registry credentials to be refreshed inside your Kubernetes cluster vi
## How it works

1. The tool runs as a pod in the `kube-system` namespace.
- It gets credentials from AWS ECR or Google Container Registry
- It gets credentials from AWS ECR, Google Container Registry, Docker private registry, or Azure Container Registry.
- Next it creates a secret with credentials for your registry
- Then it sets up this secret to be used in the `ImagePullSecrets` for the default service account
- Whenever a pod is created, this secret is attached to the pod
Expand All @@ -27,6 +27,9 @@ The following parameters are driven via Environment variables.
- TOKEN_RETRY_TYPE: The type of Timer to use when getting a registry token fails and must be retried; "simple" or "exponential" (default: simple)
- TOKEN_RETRIES: The number of times to retry getting a registry token if an error occurred (default: 3)
- TOKEN_RETRY_DELAY: The number of seconds to delay between successive retries at getting a registry token; applies to "simple" retry timer only (default: 5)
- GCRURL: URL to Google Container Registry
- DOCKER_PRIVATE_REGISTRY_SERVER, DOCKER_PRIVATE_REGISTRY_USER, DOCKER_PRIVATE_REGISTRY_PASSWORD: the URL, user name, and password for a Docker private registry
- ACR_URL, ACR_CLIENT_ID, ACR_PASSWORD: the registry URL, client ID, and password to access to access an Azure Container Registry.

## How to setup running in AWS

Expand Down Expand Up @@ -115,6 +118,30 @@ The value for `application_default_credentials.json` can be obtained with the fo
kubectl create -f k8s/replicationController.yaml
```

## How to set up Azure Container Registry

1. [Create a service principal](https://docs.microsoft.com/en-us/azure/container-registry/container-registry-auth-service-principal) that your Kubernetes cluster will use to access the registry.

2. Clone the repo and navigate to the repo root

3. Edit the sample [secret](k8s/secret.yaml) and update values for `ACR_URL`, `ACR_CLIENT_ID`, and `ACR_PASSWORD` (base64 encoded). Use service principal application ID as the client ID, and service principal password (client secret) as the password.

```bash
echo -n "secret-key" | base64
```

3. Create the secret in kubernetes

```bash
kubectl create -f k8s/secret.yml
```

4. Create the replication controller:

```bash
kubectl create -f k8s/replicationController.yaml
```

## DockerHub Image

- [upmcenterprises/registry-creds](https://hub.docker.com/r/upmcenterprises/registry-creds/)
Expand Down
16 changes: 16 additions & 0 deletions k8s/secret.yaml
Expand Up @@ -50,3 +50,19 @@ data:
gcrurl: aHR0cHM6Ly9nY3IuaW8=
type: Opaque

---

apiVersion: v1
kind: Secret
metadata:
name: registry-creds-acr
namespace: kube-system
labels:
app: registry-creds
kubernetes.io/minikube-addons: registry-creds
cloud: acr
data:
ACR_URL: Y2hhbmdlbWU=
ACR_CLIENT_ID: Y2hhbmdlbWU=
ACR_PASSWORD: Y2hhbmdlbWU=
type: Opaque
4 changes: 2 additions & 2 deletions lock.json
Expand Up @@ -287,8 +287,8 @@
},
{
"name": "github.com/stretchr/testify",
"version": "v1.1.4",
"revision": "69483b4bd14f5845b5a1e55bca19e954e827f1d0",
"version": "v1.4.0",
"revision": "221dbe5ed46703ee255b1da0dec05086f5035f62",
"packages": [
"assert"
]
Expand Down
67 changes: 65 additions & 2 deletions main.go
Expand Up @@ -44,7 +44,7 @@ import (
"golang.org/x/net/context"
"golang.org/x/oauth2"
"golang.org/x/oauth2/google"
"k8s.io/client-go/pkg/api/v1"
v1 "k8s.io/client-go/pkg/api/v1"
)

const (
Expand All @@ -56,6 +56,9 @@ const (
dockerPrivateRegistryPasswordKey = "DOCKER_PRIVATE_REGISTRY_PASSWORD"
dockerPrivateRegistryServerKey = "DOCKER_PRIVATE_REGISTRY_SERVER"
dockerPrivateRegistryUserKey = "DOCKER_PRIVATE_REGISTRY_USER"
acrURLKey = "ACR_URL"
acrClientIDKey = "ACR_CLIENT_ID"
acrPasswordKey = "ACR_PASSWORD"
tokenGenRetryTypeKey = "TOKEN_RETRY_TYPE"
tokenGenRetriesKey = "TOKEN_RETRIES"
tokenGenRetryDelayKey = "TOKEN_RETRY_DELAY"
Expand All @@ -71,11 +74,15 @@ var (
argAWSSecretName = flags.String("aws-secret-name", "awsecr-cred", `Default AWS secret name`)
argDPRSecretName = flags.String("dpr-secret-name", "dpr-secret", `Default Docker Private Registry secret name`)
argGCRSecretName = flags.String("gcr-secret-name", "gcr-secret", `Default GCR secret name`)
argACRSecretName = flags.String("acr-secret-name", "acr-secret", "Default Azure Container Registry secret name")
argGCRURL = flags.String("gcr-url", "https://gcr.io", `Default GCR URL`)
argAWSRegion = flags.String("aws-region", "us-east-1", `Default AWS region`)
argDPRPassword = flags.String("dpr-password", "", "Docker Private Registry password")
argDPRServer = flags.String("dpr-server", "", "Docker Private Registry server")
argDPRUser = flags.String("dpr-user", "", "Docker Private Registry user")
argACRURL = flags.String("acr-url", "", "Azure Container Registry URL")
argACRClientID = flags.String("acr-client-id", "", "Azure Container Registry client ID (user name)")
argACRPassword = flags.String("acr-password", "", "Azure Container Registry password (client secret)")
argRefreshMinutes = flags.Int("refresh-mins", 60, `Default time to wait before refreshing (60 minutes)`)
argSkipKubeSystem = flags.Bool("skip-kube-system", true, `If true, will not attempt to set ImagePullSecrets on the kube-system namespace`)
argAWSAssumeRole = flags.String("aws_assume_role", "", `If specified AWS will assume this role and use it to retrieve tokens`)
Expand Down Expand Up @@ -109,6 +116,7 @@ type controller struct {
ecrClient ecrInterface
gcrClient gcrInterface
dprClient dprInterface
acrClient acrInterface
}

// RetryConfig represents the number of retries + the retry delay for retrying an operation if it should fail
Expand All @@ -131,6 +139,10 @@ type gcrInterface interface {
DefaultTokenSource(ctx context.Context, scope ...string) (oauth2.TokenSource, error)
}

type acrInterface interface {
getAuthToken(registryURL, clientID, password string) (AuthToken, error)
}

func newEcrClient() ecrInterface {
sess := session.Must(session.NewSession())
awsConfig := aws.NewConfig().WithRegion(*argAWSRegion)
Expand Down Expand Up @@ -270,6 +282,35 @@ func generateSecretObj(tokens []AuthToken, isJSONCfg bool, secretName string) (*
return secret, nil
}

type acrClient struct{}

func (c acrClient) getAuthToken(registryURL, clientID, password string) (AuthToken, error) {
if registryURL == "" {
return AuthToken{}, fmt.Errorf("Azure Container Registry URL is missing; ensure %s parameter is set", acrURLKey)
}

if clientID == "" {
return AuthToken{}, fmt.Errorf("Client ID needed to access Azure Container Registry is missing; ensure %s parameter is set", acrClientIDKey)
}

if password == "" {
return AuthToken{}, fmt.Errorf("Password needed to access Azure Container Registry is missing; ensure %s paremeter is set", acrClientIDKey)
}

token := base64.StdEncoding.EncodeToString([]byte(strings.Join([]string{clientID, password}, ":")))

return AuthToken{AccessToken: token, Endpoint: registryURL}, nil
}

func (c *controller) getACRToken() ([]AuthToken, error) {
token, err := c.acrClient.getAuthToken(*argACRURL, *argACRClientID, *argACRPassword)
return []AuthToken{token}, err
}

func newACRClient() acrInterface {
return acrClient{}
}

// AuthToken represents an Access Token and an Endpoint for a registry service
type AuthToken struct {
AccessToken string
Expand Down Expand Up @@ -304,6 +345,12 @@ func getSecretGenerators(c *controller) []SecretGenerator {
SecretName: *argDPRSecretName,
})

secretGenerators = append(secretGenerators, SecretGenerator{
TokenGenFxn: c.getACRToken,
IsJSONCfg: true,
SecretName: *argACRSecretName,
})

return secretGenerators
}

Expand Down Expand Up @@ -451,6 +498,9 @@ func validateParams() {
dprPassword := os.Getenv(dockerPrivateRegistryPasswordKey)
dprServer := os.Getenv(dockerPrivateRegistryServerKey)
dprUser := os.Getenv(dockerPrivateRegistryUserKey)
acrURL := os.Getenv(acrURLKey)
acrClientID := os.Getenv(acrClientIDKey)
acrPassword := os.Getenv(acrPasswordKey)
gcrURLEnv := os.Getenv("gcrurl")

// initialize the retry configuration using command line values
Expand Down Expand Up @@ -542,6 +592,18 @@ func validateParams() {
if len(argAWSAssumeRoleEnv) > 0 {
argAWSAssumeRole = &argAWSAssumeRoleEnv
}

if len(acrURL) > 0 {
argACRURL = &acrURL
}

if len(acrClientID) > 0 {
argACRClientID = &acrClientID
}

if len(acrPassword) > 0 {
argACRPassword = &acrPassword
}
}

func handler(c *controller, ns *v1.Namespace) error {
Expand Down Expand Up @@ -592,7 +654,8 @@ func main() {
ecrClient := newEcrClient()
gcrClient := newGcrClient()
dprClient := newDprClient()
c := &controller{util, ecrClient, gcrClient, dprClient}
acrClient := newACRClient()
c := &controller{util, ecrClient, gcrClient, dprClient, acrClient}

util.WatchNamespaces(time.Duration(*argRefreshMinutes)*time.Minute, func(ns *v1.Namespace) error {
return handler(c, ns)
Expand Down

0 comments on commit 873632a

Please sign in to comment.