samirtahir91076/github-app-operator

By samirtahir91076

Updated about 2 months ago

A Kubernetes operator to get and renew a Github App access token using a custom GithubApp resource.

Image
Security
1

3.8K

samirtahir91076/github-app-operator repository overview

Lint Unit tests Coverage Status Build and push Trivy image scan Helm

github-app-operator

This is a Kubernetes operator that will generate an access token for a GithubApp and store it in a secret to use for authenticated requests to Github as the GithubApp.
It will reconcile a new access token before expiry (1hr).
See how to deploy the operator with Helm, Kustomize or from source - GitHub Source

Description

Key Features
  • Uses a custom resource GithubApp in your destination namespace.
  • Reads appId, installId, and either privateKeySecret, googlePrivateKeySecret or vaultPrivateKey defined in a GithubApp resource to request an access token from GitHub.
  • Stores the access token in a secret specified by accessTokenSecret.
Private Key Retrieval Options
1. Using a Kubernetes Secret
  • Configuration:
    • Use privateKeySecret - refers to an existing secret in the namespace holding the base64 encoded PEM of the GitHub App's private key.
    • The secret expects the field data.privateKey.
2. Using GCP Secret Manager
  • Configuration:
    • Note: You must base64 encode your private key before saving it in Secret Manager.
    • Configure with googlePrivateKeySecret - the full secret path in Secret Manager for your GitHub App secret, e.g. projects/xxxxxxxxxx/secrets/my-gh-app/versions/latest.
    • Configure Workload Identity to bind secret access permissions to the operator's Kubernetes Service Account.
    • Tested with the role roles/secretmanager.secretAccessor.
3. Using Hashicorp Vault
  • Configuration:
    • Note: You must base64 encode your private key before saving it in Vault.
    • The operator uses a short-lived JWT (10 minutes TTL) via Kubernetes Token Request API, with a defined audience.
    • It uses the JWT and Vault role to authenticate with Vault and pull the secret containing the private key.
    • Configure with the vaultPrivateKey block:
      • spec.vaultPrivateKey.mountPath - Secret mount path, e.g., secret
      • spec.vaultPrivateKey.secretPath - Secret path, e.g., githubapps/{App ID}
      • spec.vaultPrivateKey.secretKey - Secret key, e.g., privateKey
    • Configure Kubernetes auth with Vault.
    • Define a role and optionally audience, service account, namespace, etc., bound to the role.
    • Configure environment variables in the controller deployment spec:
      • VAULT_ROLE - The role bound for Kubernetes auth for the operator.
      • VAULT_ROLE_AUDIENCE - The audience bound in Vault.
      • VAULT_ADDR - FQDN of your Vault server, e.g., http://vault.default:8200.
      • Additional Vault env vars can be set, e.g., VAULT_NAMESPACE for enterprise Vault (see Vault API).
Token Reconciliation
  • Cleans-up the the access token secret it owned by a GithubApp object if deleted.
  • Reconciles an access token for a GithubApp when:
    • Modifications are made to the access token secret owned by a GithubApp.
    • Modifications are made to a GithubApp object.
    • The access token secret does not exist or lacks a status.expiresAt value.
  • Periodically checks the expiry time of the access token and reconciles a new one if the threshold is met or if the access token is invalid (checked against GitHub API).
  • Stores the expiry time of the access token in the status.expiresAt field of the GithubApp object.
  • Sets errors in the status.error field of the GithubApp object during reconciliation.
  • Skips requesting a new access token if the expiry threshold is not reached/exceeded.
  • Allows overriding the check interval and expiry threshold using deployment env vars:
    • CHECK_INTERVAL - e.g., to check every 5 minutes, set the value to 5m (default: 5m).
    • EXPIRY_THRESHOLD - e.g., to reconcile a new access token if there is less than 10 minutes left from expiry, set the value to 10m (default: 15m).
Proxy Configuration
  • Specify a proxy for GitHub and Vault using the env vars:
    • GITHUB_PROXY - e.g., http://myproxy.com:8080.
    • VAULT_PROXY_ADDR - e.g., http://myproxy.com:8080.
Rolling Upgrade
  • Optionally enable rolling upgrade to deployments in the same namespace as the GithubApp that match any of the labels defined in spec.rolloutDeployment.labels.
    • Useful for recreating pods to pick up new secret data.
Logging and Debugging
  • By default, logs are JSON formatted, and log level is set to info and error.
  • Set DEBUG_LOG to true in the manager deployment environment variable for debug level logs.
Additional Information
  • The CRD includes extra data printed with kubectl get:
    • App ID
    • Install ID
    • Expires At
    • Error
    • Access Token Secret
  • Events are recorded for:
    • Any error on reconcile for a GithubApp.
    • Creation of an access token secret.
    • Updating an access token secret.
    • Updating a deployment for rolling upgrade.

Example GithubApp Resource

Here is an example of how to define the GithubApp resource:

apiVersion: githubapp.samir.io/v1
kind: GithubApp
metadata:
  name: example-githubapp
spec:
  appId: <your-github-app-id>
  installId: <your-github-app-installation-id>
  privateKeySecret: <your-private-key-secret-name> # If using Kubernetes secret
  googlePrivateKeySecret: <your-google-secret-path> # If using GCP Secret Manager
  vaultPrivateKey: # If using Hashicorp Vault
    mountPath: <your-vault-mount-path>
    secretPath: <your-vault-secret-path>
    secretKey: <your-vault-secret-key>
  accessTokenSecret: <your-access-token-secret-name>

Example creating a secret to hold a GitHub App private key

  • Get your GithubApp private key and encode to base64
base64 -w 0 private-key.pem
  • Create a secret to hold the private key
apiVersion: v1
kind: Secret
metadata:
  name: github-app-secret
  namespace: YOUR_NAMESPACE
type: Opaque
data:
  privateKey: BASE64_ENCODED_PRIVATE_KEY

Example GithubApp object

  • Below example will setup a GithubApp and reconcile an access token in the team-1 namespace, the access token will be available to use in the secret github-app-access-token-123123
  • It authenticates with GitHub API using your private key secret like above example.
kubectl apply -f - <<EOF
apiVersion: githubapp.samir.io/v1
kind: GithubApp
metadata:
  name: GithubApp-sample
  namespace: team-1
spec:
  appId: 123123
  installId: 12312312
  privateKeySecret: github-app-secret
  accessTokenSecret: github-app-access-token-123123
EOF

Example GithubApp object with pod restart (deployment rolling upgrade) on token renew

  • Below example will upgrade deployments in the team-1 namespace when the github token is modified, matching any of labels:
    • foo: bar
    • foo2: bar2
kubectl apply -f - <<EOF
apiVersion: githubapp.samir.io/v1
kind: GithubApp
metadata:
  name: GithubApp-sample
  namespace: team-1
spec:
  appId: 123123
  installId: 12312312
  privateKeySecret: github-app-secret
  accessTokenSecret: github-app-access-token-123123
  rolloutDeployment:
    labels:
      foo: bar
      foo2: bar2
EOF

Example GithubApp object using Vault to pull the private key during run-time

  • Below example will request a new JWT from Kubernetes and use it to fetch the private key from Vault when the github access token expires
kubectl apply -f - <<EOF
apiVersion: githubapp.samir.io/v1
kind: GithubApp
metadata:
  name: GithubApp-sample
  namespace: team-1
spec:
  appId: 123123
  installId: 12312312
  accessTokenSecret: github-app-access-token-123123
  vaultPrivateKey:
    mountPath: secret
    secretPath: githubapp/123123
    secretKey: privateKey
EOF

Example GithubApp object using GCP Secret Manager to pull the private key during run-time

  • Below example will fetch the private key from GCP Secret Manager when the github access token expires
  • It requires that the Kubernetes Service Account has permissions on the secret in SEcret Manager, i.e. via Workload Identity
kubectl apply -f - <<EOF
apiVersion: githubapp.samir.io/v1
kind: GithubApp
metadata:
  name: GithubApp-sample
  namespace: team-1
spec:
  appId: 123123
  installId: 12312312
  accessTokenSecret: github-app-access-token-123123
  googlePrivateKeySecret: "projects/123123123123/secrets/gh-app-123123/versions/latest"
EOF

Getting Started

Prerequisites
  • go version v1.22.0+
  • docker version 17.03+.
  • kubectl version v1.22.0+.
  • Access to a Kubernetes v1.22.0+ cluster.
To deploy with Helm using public Docker image

A helm chart is generated using make helm when a new tag is pushed, i.e a release. You can pull the helm chart from this repos packages

  • See the packages page
  • Pull with helm:
    • helm pull oci://ghcr.io/samirtahir91/github-app-operator/helm-charts/github-app-operator --version <TAG>
      
  • Untar the chart and edit the values.yaml as required.
  • You can use the latest public image on DockerHub - samirtahir91076/github-app-operator:latest
  • Deploy the chart with Helm.

Tag summary

Content type

Image

Digest

sha256:8053ee4cf

Size

34.7 MB

Last updated

about 2 months ago

docker pull samirtahir91076/github-app-operator