Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Azure Container Registry support #82

Merged
merged 2 commits into from
Jan 21, 2020
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
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
registry-creds
registry-creds
.vscode/
29 changes: 28 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Loading