Skip to content
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
41 changes: 22 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,15 @@ The Gateway name is configured via the `GATEWAY_API_GATEWAY_NAME` environment va

### Trusted CA bundle (optional)

If the gateway needs to interact with an OIDC issuer (e.g., Keycloak) that uses a self-signed or private CA certificate, create a ConfigMap named `gateway-trusted-ca` in the control plane namespace (default: `hypershell`). The control plane copies this ConfigMap into each tenant namespace and mounts it into gateway pods so they can validate the issuer's TLS certificate when fetching JWKS keys or verifying tokens.
If the gateway needs to interact with an OIDC issuer (e.g., Keycloak) that uses a self-signed or private CA certificate, create a ConfigMap named `gateway-trusted-ca` in the control plane namespace (default: `hypershell-system`). The control plane copies this ConfigMap into each tenant namespace and mounts it into gateway pods so they can validate the issuer's TLS certificate when fetching JWKS keys or verifying tokens.

```shell
kubectl -n hypershell create configmap gateway-trusted-ca --from-file=ca-bundle.crt=/path/to/ca.crt
kubectl -n hypershell-system create configmap gateway-trusted-ca --from-file=ca-bundle.crt=/path/to/ca.crt
```

### Keycloak OIDC client provisioning (`hypershell-keycloak-admin`)

The control plane provisions an OIDC client in Keycloak for each gateway it reconciles. It authenticates to Keycloak using a confidential client whose credentials are read from a Secret named `hypershell-keycloak-admin` in the control plane namespace (default: `hypershell`). If this Secret is absent at startup, Keycloak integration is silently disabled for the lifetime of that pod.
The control plane provisions an OIDC client in Keycloak for each gateway it reconciles. It authenticates to Keycloak using a confidential client whose credentials are read from a Secret named `hypershell-keycloak-admin` in the control plane namespace (default: `hypershell-system`). If this Secret is absent at startup, Keycloak integration is silently disabled for the lifetime of that pod.

#### 1. Create a realm

Expand Down Expand Up @@ -115,7 +115,7 @@ Retrieve the generated client secret and create the Kubernetes Secret in the con
CLIENT_SECRET=$(curl -s "$KEYCLOAK_URL/admin/realms/hypershell/clients/$CLIENT_UUID/client-secret" \
-H "Authorization: Bearer $ADMIN_TOKEN" | jq -r '.value')

kubectl -n hypershell create secret generic hypershell-keycloak-admin \
kubectl -n hypershell-system create secret generic hypershell-keycloak-admin \
--from-literal=server-url="$KEYCLOAK_URL/" \
--from-literal=realm="hypershell" \
--from-literal=client-id="hypershell-control-plane" \
Expand All @@ -125,15 +125,15 @@ kubectl -n hypershell create secret generic hypershell-keycloak-admin \
If you need to rotate the client secret or update any value, delete and recreate the Secret then restart the control plane pod -- the Secret is read once at startup.

```shell
kubectl -n hypershell delete secret hypershell-keycloak-admin
kubectl -n hypershell-system delete secret hypershell-keycloak-admin
# recreate with updated values, then:
kubectl -n hypershell rollout restart deployment/hypershell-control-plane
kubectl -n hypershell-system rollout restart deployment/hypershell-controller
```

Confirm the control plane picked up the configuration:

```shell
kubectl -n hypershell logs deployment/hypershell-control-plane | grep -i keycloak
kubectl -n hypershell-system logs deployment/hypershell-controller | grep -i keycloak
# Expected: INFO keycloak integration enabled: server=... realm=hypershell
```

Expand All @@ -147,26 +147,29 @@ On OpenShift, look up the cluster's default base domain:
oc get ingresses.config.openshift.io cluster -o jsonpath='{.spec.domain}'
```

This typically returns a value like `apps.<cluster-name>.<base-domain>`. Set this value as `GATEWAY_API_BASE_DOMAIN` on the controller deployment:
This typically returns a value like `apps.<cluster-name>.<base-domain>`. Set this value as `GATEWAY_API_BASE_DOMAIN` on the controller deployment using one of:

```shell
oc set env deployment/hypershell-controller -n hypershell \
# Option 1: Patch the deployment directly
oc set env deployment/hypershell-controller -n hypershell-system \
GATEWAY_API_BASE_DOMAIN="$(oc get ingresses.config.openshift.io cluster -o jsonpath='{.spec.domain}')"
```

Or edit `components/api-server/deploy/openshift/controller.yaml` and replace the placeholder value before applying.
Or apply a kustomize patch via `deploy/openshift/kustomization.yaml` with your specific base domain value (see that file for the patch structure).

### Control plane environment variables

| Variable | Default | Description |
|---|---|---|
| `HYPERSHELL_GRPC_SERVER_ADDR` | `localhost:9000` | gRPC address of the API server |
| `HYPERSHELL_API_SERVER_URL` | `http://localhost:8000` | HTTP address of the API server |
| `HYPERSHELL_NAMESPACE` | `hypershell` | Namespace the control plane runs in (used for trusted CA bundle source) |
| `GATEWAY_API_GATEWAY_NAME` | *(required)* | Name of the pre-existing Gateway resource that tenant GRPCRoutes attach to |
| `GATEWAY_API_GATEWAY_NAMESPACE` | `openshift-ingress` | Namespace where the pre-existing Gateway resource lives |
| `GATEWAY_API_BASE_DOMAIN` | *(none)* | Base domain for tenant hostname generation (e.g., `openshell.example.com` → `gw-<ns>.openshell.example.com`) |
| `GATEWAY_MANIFESTS_DIR` | `/manifests/gateway` | Path to gateway manifest templates |
| Variable | Default | Required | Description |
|---|---|---|---|
| `HYPERSHELL_GRPC_SERVER_ADDR` | `localhost:9000` | ✓ | gRPC address of the API server |
| `HYPERSHELL_API_SERVER_URL` | `http://localhost:8000` | ✓ | HTTP address of the API server |
| `HYPERSHELL_NAMESPACE` | `hypershell-system` | ✓ | Namespace the control plane runs in (used for trusted CA bundle source) |
| `GATEWAY_IMAGE` | *(none)* | **✓ required** | Container image for tenant gateways (pinned by digest; no fallback). Set in `deploy/base/controller.yaml` |
| `GATEWAY_SUPERVISOR_IMAGE` | *(none)* | **✓ required** | Container image for gateway supervisors (pinned by digest; no fallback). Set in `deploy/base/controller.yaml` |
| `GATEWAY_API_GATEWAY_NAME` | *(required)* | ✓ | Name of the pre-existing Gateway resource that tenant GRPCRoutes attach to |
| `GATEWAY_API_GATEWAY_NAMESPACE` | `openshift-ingress` | ✓ | Namespace where the pre-existing Gateway resource lives |
| `GATEWAY_API_BASE_DOMAIN` | *(none)* | | Base domain for tenant hostname generation (e.g., `openshell.example.com` → `gw-<ns>.openshell.example.com`) |
| `GATEWAY_MANIFESTS_DIR` | `/manifests/gateway` | ✓ | Path to gateway manifest templates |

## Observability

Expand Down
73 changes: 0 additions & 73 deletions components/api-server/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -125,76 +125,3 @@ proto:
.PHONY: proto-clean
proto-clean:
rm -rf pkg/api/grpc/

KIND_CLUSTER_NAME?=hypershell-dev
KIND_API_PORT?=23080
IMAGE_NAME=hypershell
CONTROLLER_IMAGE_NAME=hypershell-controller
IMAGE_TAG=dev
REPO_ROOT=$(shell git rev-parse --show-toplevel)

.PHONY: image
image:
$(CONTAINER_ENGINE) build -t $(IMAGE_NAME):$(IMAGE_TAG) \
-f Dockerfile \
--build-arg GIT_VERSION=$(build_version) \
--build-arg BUILD_TIME="$(build_time)" \
.

.PHONY: image-controller
image-controller:
$(CONTAINER_ENGINE) build -t $(CONTROLLER_IMAGE_NAME):$(IMAGE_TAG) \
-f $(REPO_ROOT)/components/control-plane/Dockerfile \
$(REPO_ROOT)

.PHONY: images
images: image image-controller

.PHONY: kind-up
kind-up: images
kind create cluster --name $(KIND_CLUSTER_NAME) --config deploy/kind/kind-config.yaml || true
rm -f /tmp/$(IMAGE_NAME)-$(IMAGE_TAG).tar /tmp/$(CONTROLLER_IMAGE_NAME)-$(IMAGE_TAG).tar
$(CONTAINER_ENGINE) save -o /tmp/$(IMAGE_NAME)-$(IMAGE_TAG).tar $(IMAGE_NAME):$(IMAGE_TAG)
kind load image-archive /tmp/$(IMAGE_NAME)-$(IMAGE_TAG).tar --name $(KIND_CLUSTER_NAME)
$(CONTAINER_ENGINE) save -o /tmp/$(CONTROLLER_IMAGE_NAME)-$(IMAGE_TAG).tar $(CONTROLLER_IMAGE_NAME):$(IMAGE_TAG)
kind load image-archive /tmp/$(CONTROLLER_IMAGE_NAME)-$(IMAGE_TAG).tar --name $(KIND_CLUSTER_NAME)
kubectl kustomize deploy/kind/ | kubectl apply -f -
@echo "Waiting for PostgreSQL..."
kubectl wait --for=condition=ready pod -l app=hypershell-postgres -n hypershell --timeout=120s
@echo "Waiting for API server..."
kubectl wait --for=condition=available deployment/hypershell-api-server -n hypershell --timeout=120s
@echo "Waiting for controller..."
kubectl wait --for=condition=available deployment/hypershell-controller -n hypershell --timeout=120s
@echo ""
@echo "HyperShell is running!"
@echo " API: http://localhost:$(KIND_API_PORT)/api/hypershell/v1/fleets"
@echo " API Server Logs: kubectl logs -f -l app=hypershell-api-server -n hypershell"
@echo " Controller Logs: kubectl logs -f -l app=hypershell-controller -n hypershell"

.PHONY: kind-down
kind-down:
kind delete cluster --name $(KIND_CLUSTER_NAME)

.PHONY: kind-rebuild
kind-rebuild: images
rm -f /tmp/$(IMAGE_NAME)-$(IMAGE_TAG).tar /tmp/$(CONTROLLER_IMAGE_NAME)-$(IMAGE_TAG).tar
$(CONTAINER_ENGINE) save -o /tmp/$(IMAGE_NAME)-$(IMAGE_TAG).tar $(IMAGE_NAME):$(IMAGE_TAG)
kind load image-archive /tmp/$(IMAGE_NAME)-$(IMAGE_TAG).tar --name $(KIND_CLUSTER_NAME)
$(CONTAINER_ENGINE) save -o /tmp/$(CONTROLLER_IMAGE_NAME)-$(IMAGE_TAG).tar $(CONTROLLER_IMAGE_NAME):$(IMAGE_TAG)
kind load image-archive /tmp/$(CONTROLLER_IMAGE_NAME)-$(IMAGE_TAG).tar --name $(KIND_CLUSTER_NAME)
kubectl kustomize deploy/kind/ | kubectl apply -f -
kubectl rollout restart deployment/hypershell-api-server -n hypershell
kubectl rollout restart deployment/hypershell-controller -n hypershell
kubectl wait --for=condition=available deployment/hypershell-api-server -n hypershell --timeout=120s
kubectl wait --for=condition=available deployment/hypershell-controller -n hypershell --timeout=120s

.PHONY: kind-status
kind-status:
@echo "=== Cluster ==="
kubectl cluster-info --context kind-$(KIND_CLUSTER_NAME) 2>/dev/null || echo "Cluster not running"
@echo ""
@echo "=== Pods ==="
kubectl get pods -n hypershell 2>/dev/null || echo "Namespace not found"
@echo ""
@echo "=== Services ==="
kubectl get svc -n hypershell 2>/dev/null || echo "Namespace not found"
90 changes: 0 additions & 90 deletions components/api-server/deploy/ibm/controller-clusterrbac.yaml

This file was deleted.

87 changes: 0 additions & 87 deletions components/api-server/deploy/ibm/kustomization.yaml

This file was deleted.

14 changes: 0 additions & 14 deletions components/api-server/deploy/kind/api-server-nodeport.yaml

This file was deleted.

Loading
Loading