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

[WIP] Bulk updates #73

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
11 changes: 3 additions & 8 deletions .github/workflows/ci-codeql-analysis.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,9 @@ jobs:
# a pull request then we can checkout the head.
fetch-depth: 2

# If this run was triggered by a pull request event, then checkout
# the head of the pull request instead of the merge commit.
- run: git checkout HEAD^2
if: ${{ github.event_name == 'pull_request' }}

# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
Expand All @@ -51,7 +46,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v1
uses: github/codeql-action/autobuild@v2

# ℹ️ Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
Expand All @@ -65,4 +60,4 @@ jobs:
# make release

- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1
uses: github/codeql-action/analyze@v2
56 changes: 40 additions & 16 deletions .github/workflows/ci-image-build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ jobs:
steps:

- name: Check out the repo
uses: actions/checkout@v2
uses: actions/checkout@v3

# Collect Release Tag is used to to collect information needed later in the action and expose it so it can be referenced
- name: Collect Release Tag
Expand All @@ -33,23 +33,31 @@ jobs:

# Setup QEMU to support multi-arch builds
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
uses: docker/setup-qemu-action@v2

# Part of docker/build-push-action@v2; setting up the build system
# Setting up the build system
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
uses: docker/setup-buildx-action@v2

# Part of docker/build-push-action@v2; login to dockerhub
# Login to dockerhub
- name: Login to DockerHub
uses: docker/login-action@v1
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}

# Login to ghcr.io
- name: Login to GitHub Container Registry
uses: docker/login-action@v2
with:
username: ${{ github.repository_owner }}
password: ${{ secrets.GHCR_PUSH_TOKEN }}

# Push images to DockerHub & ghcr.io
- name: Build and push imageswap-init image to DockerHub
if: github.repository == 'phenixblue/imageswap-webhook'
timeout-minutes: 30
uses: docker/build-push-action@v2
uses: docker/build-push-action@v3
with:
context: ./app/imageswap-init/
# file should be specified relative to the repo root rather than relative to the context
Expand All @@ -59,7 +67,11 @@ jobs:
# push is no longer defaulted to true under v2; to push you must specify push is true
push: true
# Uses the releasetag output exposed by the Collect Release Tag step to set the tag under v2
tags: thewebroot/imageswap-init:${{ steps.prep.outputs.releasetag }},thewebroot/imageswap-init:latest
tags: |
thewebroot/imageswap-init:${{ steps.prep.outputs.releasetag }}
thewebroot/imageswap-init:latest
ghcr.io/thewebroot/imageswap-init:${{ steps.prep.outputs.releasetag }}
ghcr.io/thewebroot/imageswap-init:latest

# Build and push imageswap container image
build-imageswap-image:
Expand All @@ -69,7 +81,7 @@ jobs:
steps:

- name: Check out the repo
uses: actions/checkout@v2
uses: actions/checkout@v3

# Collect Release Tag is used to to collect information needed later in the action and expose it so it can be referenced
- name: Collect Release Tag
Expand All @@ -80,23 +92,31 @@ jobs:

# Setup QEMU to support multi-arch builds
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
uses: docker/setup-qemu-action@v2

# Part of docker/build-push-action@v2; setting up the build system
# Setting up the build system
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
uses: docker/setup-buildx-action@v2

# Part of docker/build-push-action@v2; login to dockerhub
# Login to dockerhub
- name: Login to DockerHub
uses: docker/login-action@v1
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}

# Login to ghcr.io
- name: Login to GitHub Container Registry
uses: docker/login-action@v2
with:
username: ${{ github.repository_owner }}
password: ${{ secrets.GHCR_PUSH_TOKEN }}

# Push images to DockerHub & ghcr.io
- name: Build and push imageswap image to DockerHub
if: github.repository == 'phenixblue/imageswap-webhook'
timeout-minutes: 30
uses: docker/build-push-action@v2
uses: docker/build-push-action@v3
with:
context: ./app/imageswap/
# file should be specified relative to the repo root rather than relative to the context
Expand All @@ -106,4 +126,8 @@ jobs:
# push is no longer defaulted to true under v2; to push you must specify push is true
push: true
# Uses the releasetag output exposed by the Collect Release Tag step to set the tag under v2
tags: thewebroot/imageswap:${{ steps.prep.outputs.releasetag }},thewebroot/imageswap:latest
tags: |
thewebroot/imageswap:${{ steps.prep.outputs.releasetag }}
thewebroot/imageswap:latest
ghcr.io/thewebroot/imageswap:${{ steps.prep.outputs.releasetag }}
ghcr.io/thewebroot/imageswap:latest
12 changes: 6 additions & 6 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
# NOTE: The version for both `imageswap-init` and `imageswap` should be identical for now.
# Some effort will need to be put in to be able to distringuish changes to one vs. the other
# in CI/Release steps.
IMAGESWAP_VERSION := v1.5.0
IMAGESWAP_INIT_VERSION := v1.5.0
IMAGESWAP_VERSION := v1.5.1-test1
IMAGESWAP_INIT_VERSION := v1.5.1-test1

REPO_ROOT := $(CURDIR)
APP_NAME ?= "imageswap.py"
Expand Down Expand Up @@ -194,7 +194,7 @@ release: echo
.PHONY: build-imageswap-init-latest
build-imageswap-init-latest:

$(DOCKER) build -t thewebroot/imageswap-init:latest app/imageswap-init/
$(DOCKER) build -t thewebroot/imageswap-init:latest app/imageswap-init/ --load

# Push ImageSwap-Init container image to DockerHub
.PHONY: push-imageswap-init-latest
Expand All @@ -206,7 +206,7 @@ push-imageswap-init-latest:
.PHONY: build-imageswap-latest
build-imageswap-latest:

$(DOCKER) build -t thewebroot/imageswap:latest app/imageswap/
$(DOCKER) build -t thewebroot/imageswap:latest app/imageswap/ --load

# Push ImageSwap container image to DockerHub
.PHONY: push-imageswap-latest
Expand All @@ -222,7 +222,7 @@ build-latest: build-imageswap-init-latest push-imageswap-init-latest build-image
.PHONY: build-imageswap-init-versioned
build-imageswap-init-versioned:

$(DOCKER) build -t thewebroot/imageswap-init:${IMAGESWAP_INIT_VERSION} app/imageswap-init/
$(DOCKER) build -t thewebroot/imageswap-init:${IMAGESWAP_INIT_VERSION} app/imageswap-init/ --load

# Push ImageSwap-Init container image to DockerHub
.PHONY: push-imageswap-init-versioned
Expand All @@ -234,7 +234,7 @@ push-imageswap-init-versioned:
.PHONY: build-imageswap-versioned
build-imageswap-versioned:

$(DOCKER) build -t thewebroot/imageswap:${IMAGESWAP_VERSION} app/imageswap/
$(DOCKER) build -t thewebroot/imageswap:${IMAGESWAP_VERSION} app/imageswap/ --load

# Push ImageSwap container image to DockerHub
.PHONY: push-imageswap-versioned
Expand Down
30 changes: 30 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ ImageSwap v1.4.0 has major changes

## Overview

- [Compatibility](#compatibility)
- [Prereqs](#prereqs)
- [Quickstart](#quickstart)
- [Health Check](#health-check)
Expand All @@ -61,6 +62,21 @@ ImageSwap v1.4.0 has major changes
- [Contributing](./CONTRIBUTING.md)
- [Adopters](./ADOPTERS.md)


## Compatibility

| | K8s v1.9 -> v1.18 | K8s v1.19 | K8s v1.20 | K8s v1.21 | K8s v1.22 | K8s 1.23 | K8s 1.24 |
|-------------------|-------------------|-----------|-----------|-----------|-----------|----------|----------|
| `imageswap-1.4.2` | ✓ | ✓ | +- | - | - | - | - |
| `imageswap-1.5.0` | - | ✓ | ✓ | ✓ | ✓ | +- | ??? |

Key:

* `✓` Tested to work as part of CI for this version of Kubernetes
* `+-` Should probably work. Tested at some point. There may be some slight mismatch between Python K8s client and server versions
* `-` Unlikely to work. Combination not tested
* `?` Status unknown. Not currently tested

## Prereqs

Kubernetes 1.19.0 or above with the `admissionregistration.k8s.io/v1` (or higher) API enabled. Verify that by the following command:
Expand Down Expand Up @@ -126,6 +142,20 @@ ImageSwap uses a couple of images for operation
- [imageswap-init](./app/imageswap-init/Dockerfile)
- [imageswap](./app/imageswap/Dockerfile)

NOTE: As of v1.5.1, these images are published to DockerHub and GitHub Container Registry at the following locations:

- docker.io/thewebroot/imageswap-init
- docker.io/thewebroot/imageswap
- ghcr.io/thewebroot/imageswap-init
- ghcr.io/thewebroot/imageswap

The following platforms are supported:

- linux/adm64
- linux/arm64

NOTE: Certain past versions supported the `linux/ppc64le` platform, but those have been disabled for now due to an issue with Python dependencies in the Docker Buildx QEMU environment. We will work to support `linux/ppc64le` again in the future.

### Init Container

ImageSwap uses the `imageswap-init` init-container to generate/rotate a TLS cert/key pair to secure communication between the Kubernetes API and the webhook. This action takes place on Pod startup.
Expand Down
38 changes: 15 additions & 23 deletions app/imageswap-init/imageswap-init.py

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once cert-manager generates the complete certificate chain (tls.crt, tls.key, and ca.crt), the variable imageswap_tls_rootca_secret_name should also be updated. Is it possible to check everything within the same secret instead of searching for imageswap-tls-ca?

Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@
import json
import logging
import os
import random
import sys
import time
import yaml
Expand All @@ -43,10 +42,10 @@
imageswap_tls_rootca_secret_name = "imageswap-tls-ca"
imageswap_byoc_annotation = "imageswap-byoc"
imageswap_service_name = "imageswap"
imageswap_tls_path = "/tls"
imageswap_mwc_template_path = "/mwc"
imageswap_tls_key = ""
imageswap_tls_cert = ""
imageswap_tls_path = "/tls"
imageswap_tls_key_name = os.getenv("IMAGESWAP_TLS_KEY_NAME", "tls.cert")
imageswap_tls_cert_name = os.getenv("IMAGESWAP_TLS_CERT_NAME", "tls.key")
imageswap_mwc_name = "imageswap-webhook"
imageswap_mwc_template_file = f"{imageswap_mwc_template_path}/imageswap-mwc.yaml"
imageswap_mwc_webhook_name = "imageswap.webhook.k8s.twr.io"
Expand Down Expand Up @@ -369,7 +368,7 @@ def cert_expired(namespace, tls_secret):
"""Function to check tls certificate return number of days until expiration"""

current_datetime = datetime.datetime.now()
tls_cert_decoded = base64.b64decode(tls_secret.data["cert.pem"])
tls_cert_decoded = base64.b64decode(tls_secret.data[imageswap_tls_cert_name])
tls_cert = x509.load_pem_x509_certificate(tls_cert_decoded, default_backend())
expire_days = tls_cert.not_valid_after - current_datetime

Expand All @@ -386,14 +385,11 @@ def cert_expired(namespace, tls_secret):
def cert_should_update(namespace, secret_exists, tls_secret, imageswap_tls_byoc):
"""Function to check if tls certificate should be updated"""

tls_cert_key = "cert.pem"
tls_key_key = "key.pem"

if tls_secret.data != None:

if tls_cert_key in tls_secret.data and tls_key_key in tls_secret.data:
if imageswap_tls_cert_name in tls_secret.data and imageswap_tls_key_name in tls_secret.data:

if tls_secret.data[tls_cert_key] == "" or tls_secret.data[tls_key_key] == "":
if tls_secret.data[imageswap_tls_cert_name] == "" or tls_secret.data[imageswap_tls_key_name] == "":

if imageswap_tls_byoc:

Expand Down Expand Up @@ -460,8 +456,8 @@ def read_tls_pair(namespace, secret_name, tls_pair, core_api):

return secret, tls_pair, secret_exists, False

tls_cert_pem = base64.b64decode(secret.data["cert.pem"])
tls_key_pem = base64.b64decode(secret.data["key.pem"])
tls_cert_pem = base64.b64decode(secret.data[imageswap_tls_cert_name])
tls_key_pem = base64.b64decode(secret.data[imageswap_tls_key_name])

if tls_cert_pem != "" or tls_key_pem != "":

Expand Down Expand Up @@ -532,8 +528,8 @@ def write_tls_pair(
)

secret_data = {
"cert.pem": base64.b64encode(tls_pair["cert"]).decode("utf-8").rstrip(),
"key.pem": base64.b64encode(tls_pair["key"]).decode("utf-8").rstrip(),
imageswap_tls_cert_name: base64.b64encode(tls_pair["cert"]).decode("utf-8").rstrip(),
imageswap_tls_key_name: base64.b64encode(tls_pair["key"]).decode("utf-8").rstrip(),
}

secret = client.V1Secret(metadata=secret_metadata, data=secret_data, type="tls")
Expand Down Expand Up @@ -571,8 +567,8 @@ def write_tls_pair(
}

secret.data = {
"cert.pem": base64.b64encode(tls_pair["cert"]).decode("utf-8").rstrip(),
"key.pem": base64.b64encode(tls_pair["key"]).decode("utf-8").rstrip(),
imageswap_tls_cert_name: base64.b64encode(tls_pair["cert"]).decode("utf-8").rstrip(),
imageswap_tls_key_name: base64.b64encode(tls_pair["key"]).decode("utf-8").rstrip(),
}

try:
Expand Down Expand Up @@ -601,12 +597,12 @@ def write_tls_pair(

# Write cert and key to files for Flask/OPA containers
logging.info("Writing cert and key locally")
logging.debug(f"TLS Pair: {tls_pair}")
#logging.debug(f"TLS Pair: {tls_pair}")

with open(f"{imageswap_tls_path}/cert.pem", "wb") as cert_file:
with open(f"{imageswap_tls_path}/{imageswap_tls_cert_name}", "wb") as cert_file:
cert_file.write(tls_pair["cert"])

with open(f"{imageswap_tls_path}/key.pem", "wb") as key_file:
with open(f"{imageswap_tls_path}/{imageswap_tls_key_name}", "wb") as key_file:
key_file.write(tls_pair["key"])


Expand Down Expand Up @@ -1120,10 +1116,6 @@ def main():
)

logging.info("ImageSwap Init")
# Wait random time to help alleviate race conditions with multiple
# replicas on startup
# wait_time = random.randint(1,10)
# time.sleep(wait_time)
init_tls_pair(imageswap_namespace_name)
init_mwc(imageswap_namespace_name, imageswap_tls_byoc)
logging.info("Done")
Expand Down
11 changes: 9 additions & 2 deletions app/imageswap/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,16 @@
# See the License for the specific language governing permissions and
# limitations under the License.

import os

# Set Global variables
imageswap_tls_path = "/tls"
imageswap_tls_key_name = os.getenv("IMAGESWAP_TLS_KEY_NAME", "tls.cert")
imageswap_tls_cert_name = os.getenv("IMAGESWAP_TLS_CERT_NAME", "tls.key")

# Gunicorn config
bind = ":5000"
workers = 2
threads = 2
certfile = "/tls/cert.pem"
keyfile = "/tls/key.pem"
certfile = f"{imageswap_tls_path}/{imageswap_tls_cert_name}"
keyfile = f"{imageswap_tls_path}/{imageswap_tls_key_name}"
Loading