devel: add full-stack development environment

This integrates all the Image Builder components needed by the
cloud.redhat.com frontend and allows them to be developed and run
locally using `docker compose`.

This should make it simple to make patches across the different
components and develop them in tandem.

Thanks to Achilleas Koutsou for the initial idea and implementation
in osbuild-composer.

Signed-off-by: Tom Gundersen <teg@jklm.no>
This commit is contained in:
Tom Gundersen 2021-05-21 14:10:32 +01:00 committed by Sanne Raymaekers
parent 082894e9dd
commit 886bd768dc
10 changed files with 490 additions and 56 deletions

View file

@ -31,62 +31,8 @@ against the chrome and backend at cloud.redhat.com.
The UI should be running on
https://prod.foo.redhat.com:1337/apps/image-builder/landing.
## Backend Development
To develop both the frontend and the backend you can again use the proxy to run both the
frontend and backend locally against the chrome at cloud.redhat.com. In addition to the
above:
1. Clone the image-builder (backend) repository: https://github.com/osbuild/image-builder
2. Setting up the proxy
As before, choose a runner (podman or docker), and point the SPANDX_CONFIG variable to
`profile/local-frontend-and-api-with-identity.js` included in
image-builder-frontend.
```
sudo insights-proxy/scripts/patch-etc-hosts.sh
export RUNNER="podman"
export SPANDX_CONFIG=$PATH_TO/image-builder-frontend/profiles/local-frontend-and-api-with-identity.js
sudo -E insights-proxy/scripts/run.sh
```
3. Setting up osbuild-composer(-api)
The easiest way to do this is to call `schutzbots/provision-composer.sh` from
the `osbuild/image-builder` project. This will install composer, generate
the needed certs, and put the configuration in place.
4. Starting up image-builder
Point the URL to wherever composer is hosted, the client certificates and CA
should be reused or copied over from the composer host, they're located in
`/etc/osbuild-composer`.
In the image-builder checkout directory
```
make build
OSBUILD_URL="https://$composer-url:$composer-port/api/composer/v1" \
OSBUILD_CERT_PATH=/path/to/client-crt.pem \
OSBUILD_KEY_PATH=/path/to/client-key.pem \
OSBUILD_CA_PATH=/path/to/ca-crt.pem \
./image-builder
```
5. Starting up image-builder-frontend
In the image-builder-frontend checkout directory
```
npm install
npm start
```
The UI should be running on
https://prod.foo.redhat.com:1337/apps/image-builder/landing, the api
(image-builder) on
https://prod.foo.redhat.com:1337/api/image-builder/v1/openapi.json
frontend and backend locally against the chrome at cloud.redhat.com. For instructions
see [devel/README.md](devel/README.md).

5
devel/.env Normal file
View file

@ -0,0 +1,5 @@
COMPOSE_PROJECT_NAME=image-builder
STATE_DIR=./state
COMPOSER_CONFIG_DIR=./config/composer
WORKER_CONFIG_DIR=./config/worker
SPANDX_CONFIG=./config/spandx/local-frontend-and-api.js

1
devel/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
state

45
devel/README.md Normal file
View file

@ -0,0 +1,45 @@
# devtools
Development Tools for Image Builder
## Setup
To start local development, first clone the image bulider stack:
```bash
git clone git@github.com:osbuild/osbuild.git
git clone git@github.com:osbuild/osbuild-composer.git
git clone git@github.com:osbuild/image-builder.git
git clone git@github.com:osbuild/image-builder-frontend.git
```
Secondly redirect a few domains to localhost. One for each environment
of cloud.redhat.com that exists. You only need the ones you will be
developing against. If you are outside the Red Hat VPN, only `prod` is
available:
```bash
echo "127.0.0.1 prod.foo.redhat.com" >> /etc/hosts
echo "127.0.0.1 qa.foo.redhat.com" >> /etc/hosts
echo "127.0.0.1 ci.foo.redhat.com" >> /etc/hosts
echo "127.0.0.1 stage.foo.redhat.com" >> /etc/hosts
```
Lastly run the setup tool from image-builder-frontend to generate TLS certs
and potentially other runtime configuration.
```bash
cd image-builder-frontend/devel
./setup.sh
```
## Run
```bash
docker compose up
```
Access the service through the GUI:
[http://prod.foo.redhat.com:1337/beta/](http://prod.foo.redhat.com:1337/beta/), or
directly through the API:
[https://prod.foo.redhat.com:1337/docs/api/image-builder](https://prod.foo.redhat.com:1337/docs/api/image-builder).

View file

@ -0,0 +1,7 @@
[worker]
allowed_domains = [ "localhost", "worker.osbuild.org" ]
ca = "/etc/osbuild-composer/ca-crt.pem"
[koji]
allowed_domains = [ "client.osbuild.org" ]
ca = "/etc/osbuild-composer/ca-crt.pem"

View file

@ -0,0 +1,127 @@
/*global module*/
const jwt = require('jsonwebtoken');
const cookie = require('cookie');
const fs = require('fs');
const base64 = require('base-64');
const SECTION = 'insights';
const APP_ID = 'image-builder';
const FRONTEND_PORT = 8002;
const API_PORT = 8086;
const routes = {};
const PORTAL_BACKEND_MARKER = 'PORTAL_BACKEND_MARKER';
const keycloakPubkeys = {
prod: fs.readFileSync('/certs/keycloak.prod.cert', 'utf8'),
stage: fs.readFileSync('/certs/keycloak.stage.cert', 'utf8'),
qa: fs.readFileSync('/certs/keycloak.qa.cert', 'utf8')
};
const buildUser = input => {
const user = {
entitlements: {
insights: { is_entitled: true },
smart_management: { is_entitled: true },
openshift: { is_entitled: true },
hybrid: { is_entitled: true },
migrations: { is_entitled: true },
ansible: { is_entitled: true }
},
identity: {
account_number: input.account_number,
type: 'User',
user: {
username: input.username,
email: input.email,
first_name: input.first_name,
last_name: input.last_name,
is_active: true,
is_org_admin: input.is_org_admin,
is_internal: input.is_internal,
locale: input.locale
},
internal: {
org_id: input.account_id
}
}
};
return user;
};
const envMap = {
ci: {
keycloakPubkey: keycloakPubkeys.qa,
target: 'https://ci.cloud.redhat.com',
str: 'ci'
},
qa: {
keycloakPubkey: keycloakPubkeys.qa,
target: 'https://qa.cloud.redhat.com',
str: 'qa'
},
stage: {
keycloakPubkey: keycloakPubkeys.stage,
target: 'https://stage.cloud.redhat.com',
str: 'stage'
},
prod: {
keycloakPubkey: keycloakPubkeys.prod,
target: 'https://cloud.redhat.com',
str: 'prod'
}
};
const authPlugin = (req, res, target) => {
let env = envMap.prod;
switch (req.headers['x-spandx-origin']) {
case 'ci.foo.redhat.com': env = envMap.ci; break;
case 'qa.foo.redhat.com': env = envMap.qa; break;
case 'stage.foo.redhat.com': env = envMap.stage; break;
case 'prod.foo.redhat.com': env = envMap.prod; break;
default: env = false;
}
if (target === PORTAL_BACKEND_MARKER) {
target = env.target;
console.log(` --> mangled ${PORTAL_BACKEND_MARKER} to ${target}`);
}
const noop = { then: (cb) => { cb(target); } };
if (!req || !req.headers || !req.headers.cookie) { return noop; } // no cookies short circut
const cookies = cookie.parse(req.headers.cookie);
if (!cookies.cs_jwt) { return noop; } // no rh_jwt short circut
var decoded = jwt.decode(cookies.cs_jwt);
const user = buildUser(decoded);
const unicodeUser = new Buffer(JSON.stringify(user), "utf8");
req.headers["x-rh-identity"] = unicodeUser.toString("base64");
return new Promise((resolve, reject) => resolve(target));
};
routes[`/beta/${SECTION}/${APP_ID}`] = { host: `http://frontend:${FRONTEND_PORT}` };
routes[`/${SECTION}/${APP_ID}`] = { host: `http://frontend:${FRONTEND_PORT}` };
routes[`/beta/apps/${APP_ID}`] = { host: `http://frontend:${FRONTEND_PORT}` };
routes[`/apps/${APP_ID}`] = { host: `http://frontend:${FRONTEND_PORT}` };
routes[`/api/${APP_ID}`] = { host: `http://backend:${API_PORT}` };
routes['/apps/chrome'] = { host: PORTAL_BACKEND_MARKER };
routes['/apps/beta/chrome'] = { host: PORTAL_BACKEND_MARKER };
module.exports = {
bs: {
notify: false,
https: {
key: '/ssl/key.pem',
cert: '/ssl/cert.pem'
}
},
routerPlugin: authPlugin,
routes: routes,
};

View file

@ -0,0 +1,85 @@
#
# ca options
#
[ca]
default_ca = osbuild_ca
[osbuild_ca]
database = ./index.txt
new_certs_dir = ./certs
rand_serial = yes
certificate = ca.cert.pem
private_key = private/ca.key.pem
default_days = 3650
default_md = sha256
x509_extensions = osbuild_ca_ext
# See WARNINGS in `man openssl ca`. This is ok, becasue it only copies
# extensions that are not already specified in `osbuild_ca_ext`.
copy_extensions = copy
preserve = no
policy = osbuild_ca_policy
# We want to issue multiple certificates with the same subject in the
# testing environment.
unique_subject = no
[osbuild_ca_ext]
basicConstraints = critical, CA:TRUE
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always, issuer:always
keyUsage = critical, digitalSignature, cRLSign, keyCertSign
[osbuild_ca_policy]
commonName = supplied
emailAddress = supplied
#
# Extensions for server certificates
#
[osbuild_server_ext]
basicConstraints = critical, CA:FALSE
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid, issuer:always
keyUsage = critical, digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth
#
# Extensions for client certificates
#
[osbuild_client_ext]
basicConstraints = CA:FALSE
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer
keyUsage = critical, nonRepudiation, digitalSignature, keyEncipherment
extendedKeyUsage = clientAuth
#
# req options
#
[req]
default_md = sha256
default_bits = 2048
distinguished_name = osbuild_distinguished_name
#
# Only prompt for CN
#
[osbuild_distinguished_name]
CN = Common Name
emailAddress = E-Mail Address

115
devel/docker-compose.yml Normal file
View file

@ -0,0 +1,115 @@
version: '2.4'
services:
composer:
image: local/osbuild-composer
build:
context: ../../osbuild-composer
dockerfile: ./distribution/Dockerfile-ubi
volumes:
- ${COMPOSER_CONFIG_DIR}/osbuild-composer.toml:/etc/osbuild-composer/osbuild-composer.toml
- ${STATE_DIR}/x509/ca-crt.pem:/etc/osbuild-composer/ca-crt.pem
- ${STATE_DIR}/x509/composer-crt.pem:/etc/osbuild-composer/composer-crt.pem
- ${STATE_DIR}/x509/composer-key.pem:/etc/osbuild-composer/composer-key.pem
networks:
net:
ipv4_address: 172.31.0.10
worker:
image: local/osbuild-worker
build:
context: ../../osbuild-composer
dockerfile: ./distribution/Dockerfile-worker
# override the entrypoint to specify composer hostname and port
entrypoint: /usr/libexec/osbuild-composer/osbuild-worker composer:8700
volumes:
- ${STATE_DIR}/x509/ca-crt.pem:/etc/osbuild-composer/ca-crt.pem
- ${STATE_DIR}/x509/worker-crt.pem:/etc/osbuild-composer/worker-crt.pem
- ${STATE_DIR}/x509/worker-key.pem:/etc/osbuild-composer/worker-key.pem
environment:
- CACHE_DIRECTORY=/var/cache/osbuild-composer
privileged: true
cap_add:
- MKNOD
- SYS_ADMIN
- NET_ADMIN
networks:
net:
ipv4_address: 172.31.0.20
depends_on:
- "composer"
postgres:
image: postgres:10.5
environment:
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=postgres
volumes:
- ../../image-builder/internal/db/migrations/1_create_table_images.up.sql:/docker-entrypoint-initdb.d/create_tables.sql
networks:
net:
ipv4_address: 172.31.0.30
backend:
image: local/image-builder
build:
context: ../../image-builder
dockerfile: ./distribution/Dockerfile-ubi
volumes:
- ${STATE_DIR}/x509/ca-crt.pem:/etc/image-builder/ca-crt.pem
- ${STATE_DIR}/x509/client-crt.pem:/etc/image-builder/client-crt.pem
- ${STATE_DIR}/x509/client-key.pem:/etc/image-builder/client-key.pem
environment:
- LISTEN_ADDRESS=backend:8086
- LOG_LEVEL=DEBUG
- ALLOWED_ORG_IDS=*
- PGHOST=postgres
- PGPORT=5432
- PGDATABASE=postgres
- PGUSER=postgres
- PGPASSWORD=postgres
- OSBUILD_URL=https://composer:9196
- DISTRIBUTIONS_DIR=/app/distributions
- OSBUILD_CERT_PATH=/etc/image-builder/client-crt.pem
- OSBUILD_KEY_PATH=/etc/image-builder/client-key.pem
- OSBUILD_CA_PATH=/etc/image-builder/ca-crt.pem
networks:
net:
ipv4_address: 172.31.0.40
depends_on:
- "composer"
- "postgres"
frontend:
image: local/image-builder-frontend
build:
context: ../../image-builder-frontend
dockerfile: ./distribution/Dockerfile
environment:
- HOST=frontend
networks:
net:
ipv4_address: 172.31.0.50
insightsproxy:
image: redhatinsights/insights-proxy:latest
security_opt:
- label=disable
environment:
- CUSTOM_CONF=true
volumes:
- ${SPANDX_CONFIG}:/config/spandx.config.js
extra_hosts:
- "prod.foo.redhat.com:127.0.0.1"
- "qa.foo.redhat.com:127.0.0.1"
- "ci.foo.redhat.com:127.0.0.1"
- "stage.foo.redhat.com:127.0.0.1"
networks:
net:
ipv4_address: 172.31.0.60
ports:
- 1337:1337
depends_on:
- "backend"
- "frontend"
networks:
net:
ipam:
driver: default
config:
- subnet: 172.31.0.0/16

97
devel/gen-certs.sh Executable file
View file

@ -0,0 +1,97 @@
#!/bin/bash
if (( $# != 3 )); then
echo "Usage: $0 <openssl-config> <certdir> <cadir>"
echo
echo "Positional arguments"
echo " <openssl-config> OpenSSL configuration file"
echo " <certdir> Destination directory for the generated files"
echo " <cadir> Working directory for the generation process"
exit 1
fi
set -euxo pipefail
# Generate all X.509 certificates for the tests
# The whole generation is done in a $CADIR to better represent how osbuild-ca
# it.
OPENSSL_CONFIG="$1"
CERTDIR="$2"
CADIR="$3"
# The $CADIR might exist from a previous test (current Schutzbot's imperfection)
rm -rf "$CADIR" || true
mkdir -p "$CADIR" "$CERTDIR"
# Convert the arguments to real paths so we can safely change working directory
OPENSSL_CONFIG="$(realpath "${OPENSSL_CONFIG}")"
CERTDIR="$(realpath "${CERTDIR}")"
CADIR="$(realpath "${CADIR}")"
pushd "$CADIR"
mkdir certs private
touch index.txt
# Generate a CA.
openssl req -config "$OPENSSL_CONFIG" \
-keyout private/ca.key.pem \
-new -nodes -x509 -extensions osbuild_ca_ext \
-out ca.cert.pem -subj "/CN=osbuild.org"
# Copy the private key to the location expected by the tests
cp ca.cert.pem "$CERTDIR"/ca-crt.pem
# Generate a composer certificate.
openssl req -config "$OPENSSL_CONFIG" \
-keyout "$CERTDIR"/composer-key.pem \
-new -nodes \
-out /tmp/composer-csr.pem \
-subj "/CN=localhost/emailAddress=osbuild@example.com" \
-addext "subjectAltName=DNS:localhost, DNS:composer"
openssl ca -batch -config "$OPENSSL_CONFIG" \
-extensions osbuild_server_ext \
-in /tmp/composer-csr.pem \
-out "$CERTDIR"/composer-crt.pem
# Generate a worker certificate.
openssl req -config "$OPENSSL_CONFIG" \
-keyout "$CERTDIR"/worker-key.pem \
-new -nodes \
-out /tmp/worker-csr.pem \
-subj "/CN=localhost/emailAddress=osbuild@example.com" \
-addext "subjectAltName=DNS:localhost, DNS:worker"
openssl ca -batch -config "$OPENSSL_CONFIG" \
-extensions osbuild_client_ext \
-in /tmp/worker-csr.pem \
-out "$CERTDIR"/worker-crt.pem
# Generate a client certificate.
openssl req -config "$OPENSSL_CONFIG" \
-keyout "$CERTDIR"/client-key.pem \
-new -nodes \
-out /tmp/client-csr.pem \
-subj "/CN=client.osbuild.org/emailAddress=osbuild@example.com" \
-addext "subjectAltName=DNS:client.osbuild.org"
openssl ca -batch -config "$OPENSSL_CONFIG" \
-extensions osbuild_client_ext \
-in /tmp/client-csr.pem \
-out "$CERTDIR"/client-crt.pem
# Client keys are used by tests to access the composer APIs. Allow all users access.
chmod 644 "$CERTDIR"/client-key.pem
# Generate a kojihub certificate.
openssl req -config "$OPENSSL_CONFIG" \
-keyout "$CERTDIR"/kojihub-key.pem \
-new -nodes \
-out /tmp/kojihub-csr.pem \
-subj "/CN=localhost/emailAddress=osbuild@example.com" \
-addext "subjectAltName=DNS:localhost"
openssl ca -batch -config "$OPENSSL_CONFIG" \
-extensions osbuild_server_ext \
-in /tmp/kojihub-csr.pem \
-out "$CERTDIR"/kojihub-crt.pem
popd

6
devel/setup.sh Executable file
View file

@ -0,0 +1,6 @@
#!/bin/bash
./gen-certs.sh \
config/x509/openssl.cnf \
state/x509 \
state/x509/ca