Skip to content

Commit

Permalink
feat: add registry authentifaction
Browse files Browse the repository at this point in the history
  • Loading branch information
azrod committed Sep 30, 2024
1 parent a712fb1 commit cff8a20
Show file tree
Hide file tree
Showing 29 changed files with 1,561 additions and 47 deletions.
2 changes: 0 additions & 2 deletions .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,2 @@
ARG VARIANT="1-bullseye"
FROM mcr.microsoft.com/vscode/devcontainers/go:${VARIANT}

# RUN pip install mkdocs mkdocs-material pymdown-extensions mkdocs-video
4 changes: 3 additions & 1 deletion .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,9 @@
"ghcr.io/devcontainers/features/python:1": {
"installTools": false
},
"ghcr.io/gvatsal60/dev-container-features/pre-commit:1": {}
"ghcr.io/gvatsal60/dev-container-features/pre-commit:1": {},
"ghcr.io/devcontainers/features/docker-outside-of-docker:1": {},
"ghcr.io/rio/features/kustomize:1": {}
},
"workspaceMount": "source=${localWorkspaceFolder},target=/workspace,type=bind",
"workspaceFolder": "/workspace"
Expand Down
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ examples/
bin/
dist/
config/manifests/mutatingWebhookConfiguration.yaml
site/
site/
**/charts/**
5 changes: 5 additions & 0 deletions api/v1alpha1/image_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ type (
// +kubebuilder:validation:Optional
ImagePullSecrets []corev1.LocalObjectReference `json:"imagePullSecrets,omitempty"`

// +kubebuilder:validation:Optional
// +kubebuilder:default:=false
// +kubebuilder:example:=true
InsecureSkipTLSVerify bool `json:"insecureSkipTLSVerify,omitempty"`

// +kubebuilder:validation:Optional
// +kubebuilder:default:="latest"
// +kubebuilder:example:="v1.2.0"
Expand Down
30 changes: 28 additions & 2 deletions cmd/kimup/scheduler.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,14 @@ import (
"github.com/orange-cloudavenue/kube-image-updater/internal/rules"
"github.com/orange-cloudavenue/kube-image-updater/internal/triggers"
"github.com/orange-cloudavenue/kube-image-updater/internal/triggers/crontab"
"github.com/orange-cloudavenue/kube-image-updater/internal/utils"
)

func initScheduler(k *kubeclient.Client) {
// Start Crontab client
crontab.New(context.Background())

event.On(triggers.RefreshImage.String(), event.ListenerFunc(func(e event.Event) error {
event.On(triggers.RefreshImage.String(), event.ListenerFunc(func(e event.Event) (err error) {
retryErr := retry.RetryOnConflict(retry.DefaultBackoff, func() error {
// TODO: implement image refresh
log.Infof("Refreshing image %s in namespace %s", e.Data()["image"], e.Data()["namespace"])
Expand All @@ -39,7 +40,32 @@ func initScheduler(k *kubeclient.Client) {
// an := annotations.New(ctx, &image)
// TODO add last refresh annotation

re, err := registry.New(ctx, image.Spec.Image)
var auths kubeclient.K8sDockerRegistrySecretData

if image.Spec.ImagePullSecrets != nil {
auths, err = k.GetPullSecretsForImage(ctx, image)
if err != nil {
return err
}
}

i := utils.ImageParser(image.Spec.Image)

re, err := registry.New(ctx, image.Spec.Image, registry.Settings{
InsecureTLS: image.Spec.InsecureSkipTLSVerify,
Username: func() string {
if v, ok := auths.Auths[i.GetRegistry()]; ok {
return v.Username
}
return ""
}(),
Password: func() string {
if v, ok := auths.Auths[i.GetRegistry()]; ok {
return v.Password
}
return ""
}(),
})
if err != nil {
return err
}
Expand Down
4 changes: 4 additions & 0 deletions config/crd/bases/kimup.cloudavenue.io_images.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@ spec:
type: object
x-kubernetes-map-type: atomic
type: array
insecureSkipTLSVerify:
default: false
example: true
type: boolean
rules:
items:
description: ImageRule
Expand Down
6 changes: 2 additions & 4 deletions config/manifests/mutatingWebhookConfiguration.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ metadata:
name: mutating-webhook-configuration
namespace: default
webhooks:
- sideEffects: None
- sideEffects: None
admissionReviewVersions: ["v1"]
name: your.example.com
clientConfig:
Expand All @@ -22,10 +22,8 @@ webhooks:
apiGroups: [""]
apiVersions: ["v1"]
resources: ["pods"]
scope: "Namespaced"
scope: "Namespaced"
# namespaceSelector:
# matchExpressions:
# - {key: "name", operator: "In", values: ["core-services"]}
failurePolicy: Fail


66 changes: 66 additions & 0 deletions docs/crd/image.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# Custom Resource Definition `Image`

This is a custom resource definition for an image. It is used to store information about an image.
`Image` is a namespaced resource.

## Basic example

```yaml
apiVersion: kimup.cloudavenue.io/v1alpha1
kind: Image
metadata:
name: image-sample
spec:
image: alpine
baseTag: v1.0.0
triggers:
- <trigger>
- <trigger>
rules:
- <rule>
- <rule>
```
## Advanced
### Use authenticated registry
Use the `imagePullSecrets` field to specify the name of the secret to use to authenticate with the registry.

```yaml
apiVersion: kimup.cloudavenue.io/v1alpha1
kind: Image
metadata:
name: image-sample
spec:
image: custom-registry.io/image
baseTag: v1.0.0
imagePullSecrets:
- name: registry-local
triggers:
- <trigger>
- <trigger>
rules:
- <rule>
- <rule>
```

### Self-signed certificate

Use the `insecureSkipTLSVerify` field to skip the verification of the TLS certificate.

```yaml
kind: Image
metadata:
name: image-sample
spec:
image: custom-registry.io/image
baseTag: v1.0.0
insecureSkipTLSVerify: true
triggers:
- <trigger>
- <trigger>
rules:
- <rule>
- <rule>
```
13 changes: 10 additions & 3 deletions docs/howto.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
---
hide:
- toc
---

# HowTo

## How to Use
Expand Down Expand Up @@ -31,6 +36,7 @@ spec:
```bash
kubectl apply -f image.yaml
```

In this example the image `ghcr.io/orange-cloudavenue/kube-image-updater` will be updated every 12 hours with the latest minor version.

3 - Check the Image TAG:
Expand All @@ -39,8 +45,9 @@ In this example the image `ghcr.io/orange-cloudavenue/kube-image-updater` will b
kubectl get image demo'
NAME IMAGE TAG
demo ghcr.io/azrod/golink
demo ghcr.io/azrod/golink
```

But you can force the update by running the following command:

```bash
Expand Down Expand Up @@ -69,6 +76,8 @@ spec:
app: golink
template:
metadata:
annotations:
kimup.cloudavenue.io/enabled: "true"
labels:
app: golink
spec:
Expand All @@ -86,5 +95,3 @@ kubectl apply -f deployment.yaml
```

Now the deployment is running with the image `ghcr.io/azrod/golink:v0.1.0` define by your rules in the CRD `Image`.


1 change: 0 additions & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,4 +77,3 @@ Type of **Actions**:
- **refresh**: Apply the new image to the resource.
- **notify**: Notify a webhook with the new image.(Not implemented yet)
- **request-approval**: Request approval to apply the new image.(Not implemented yet)

3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ go 1.22.0

require (
github.com/Masterminds/semver/v3 v3.3.0
github.com/containers/image/v5 v5.30.1
github.com/crazy-max/diun/v4 v4.28.0
github.com/gookit/event v1.1.2
github.com/onsi/ginkgo/v2 v2.20.2
github.com/onsi/gomega v1.34.2
github.com/reugn/go-quartz v0.12.0
github.com/sirupsen/logrus v1.9.3
github.com/stretchr/testify v1.9.0
github.com/thanhpk/randstr v1.0.6
k8s.io/api v0.31.1
k8s.io/apimachinery v0.31.1
k8s.io/client-go v0.31.1
Expand All @@ -25,7 +27,6 @@ require (
github.com/blang/semver/v4 v4.0.0 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/containers/image/v5 v5.30.1 // indirect
github.com/containers/libtrust v0.0.0-20230121012942-c1716e8a8d01 // indirect
github.com/containers/ocicrypt v1.1.9 // indirect
github.com/containers/storage v1.53.0 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,8 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 h1:kdXcSzyDtseVEc4yCz2qF8ZrQvIDBJLl4S1c3GCXmoI=
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
github.com/thanhpk/randstr v1.0.6 h1:psAOktJFD4vV9NEVb3qkhRSMvYh4ORRaj1+w/hn4B+o=
github.com/thanhpk/randstr v1.0.6/go.mod h1:M/H2P1eNLZzlDwAzpkkkUvoyNNMbzRGhESZuEQk3r0U=
github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc=
github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/vbatts/tar-split v0.11.5 h1:3bHCTIheBm1qFTcgh9oPu+nNBtX+XJIupG/vacinCts=
Expand Down
67 changes: 50 additions & 17 deletions internal/kubeclient/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@ package kubeclient

import (
"context"
"encoding/json"
"flag"
"fmt"
"strings"

log "github.com/sirupsen/logrus"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
Expand Down Expand Up @@ -155,23 +159,6 @@ func (c *Client) SetImage(ctx context.Context, image v1alpha1.Image) (err error)
return fmt.Errorf("failed to update resource: %w", err)
}

// xImage := v1alpha1.Image{}
// if err := runtime.DefaultUnstructuredConverter.
// FromUnstructured(x.UnstructuredContent(), &xImage); err != nil {
// return fmt.Errorf("failed to convert resource: %w", err)
// }

// xImage.Status = image.Status
// xUnstructedImage, err := runtime.DefaultUnstructuredConverter.
// ToUnstructured(&image)
// if err != nil {
// return fmt.Errorf("failed to convert resource: %w", err)
// }

// if _, err := c.cImage().Namespace(image.Namespace).UpdateStatus(ctx, &unstructured.Unstructured{Object: xUnstructedImage}, metav1.UpdateOptions{}); err != nil {
// return fmt.Errorf("failed to update status: %w", err)
// }

return
}

Expand All @@ -189,3 +176,49 @@ func (c *Client) FindImage(ctx context.Context, namespace, name string) (image v

return image, fmt.Errorf("image not found")
}

type K8sDockerRegistrySecretData struct {
Auths map[string]K8sDockerRegistrySecret `json:"auths"`
}

type K8sDockerRegistrySecret struct {
Username string `json:"username"`
Password string `json:"password"`
Email string `json:"email,omitempty"`
Auth string `json:"auth"`
}

func (c *Client) GetPullSecretsForImage(ctx context.Context, image v1alpha1.Image) (auths K8sDockerRegistrySecretData, err error) {
auths.Auths = make(map[string]K8sDockerRegistrySecret)

for _, ip := range image.Spec.ImagePullSecrets {
secret, err := c.GetKubeClient().CoreV1().Secrets(image.Namespace).Get(ctx, ip.Name, metav1.GetOptions{})
if err != nil {
continue
}

if secret.Type != v1.SecretTypeDockerConfigJson {
continue
}

auth := K8sDockerRegistrySecretData{}
if err := json.Unmarshal(secret.Data[v1.DockerConfigJsonKey], &auth); err != nil {
return auths, fmt.Errorf("failed to unmarshal secret: %w", err)
}

for k, v := range auth.Auths {
if v.Username == "" || v.Password == "" {
continue
}

for _, i := range []string{"https://", "http://"} {
k = strings.TrimPrefix(k, i)
}

log.Debugf("Found auth for %s", k)
auths.Auths[k] = v
}
}

return auths, nil
}
1 change: 1 addition & 0 deletions internal/registry/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
FROM scratch
18 changes: 16 additions & 2 deletions internal/registry/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"errors"

"github.com/containers/image/v5/types"
dRegistry "github.com/crazy-max/diun/v4/pkg/registry"
)

Expand All @@ -19,9 +20,15 @@ type (
r *dRegistry.Client
dR dRegistry.Image
}

Settings struct {
InsecureTLS bool
Username string
Password string
}
)

func New(ctx context.Context, repo string) (*Repository, error) {
func New(ctx context.Context, repo string, settings Settings) (*Repository, error) {
if repo == "" {
return nil, ErrRepoIsEmpty
}
Expand All @@ -33,7 +40,14 @@ func New(ctx context.Context, repo string) (*Repository, error) {
return nil, ErrInvalidRepo
}

r, err := dRegistry.New(dRegistry.Options{})
r, err := dRegistry.New(dRegistry.Options{
Auth: types.DockerAuthConfig{
Username: settings.Username,
Password: settings.Password,
},
InsecureTLS: settings.InsecureTLS,
UserAgent: "kube-image-updater",
})
if err != nil {
return nil, err
}
Expand Down
Loading

0 comments on commit cff8a20

Please sign in to comment.