Skip to content

Commit 66a1bde

Browse files
authored
feat(api): adds initial auth to endpoints (#181)
1 parent 6cd8e54 commit 66a1bde

File tree

18 files changed

+357
-74
lines changed

18 files changed

+357
-74
lines changed

foundry/api/Earthfile

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,4 +95,21 @@ docker:
9595
COPY entrypoint.sh .
9696

9797
ENTRYPOINT [ "/bin/bash", "/app/entrypoint.sh" ]
98-
SAVE IMAGE ${container}:${tag}
98+
SAVE IMAGE ${container}:${tag}
99+
100+
certs:
101+
FROM +build
102+
103+
RUN ./bin/foundry-api auth init --output-dir /certs
104+
105+
SAVE ARTIFACT /certs certs
106+
107+
jwt:
108+
FROM +build
109+
110+
COPY --dir certs .
111+
112+
RUN mkdir -p /jwt
113+
RUN ./bin/foundry-api auth generate -a -k ./certs/private.pem > /jwt/token.txt
114+
115+
SAVE ARTIFACT /jwt jwt

foundry/api/blueprint.cue

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,12 @@ project: {
4646
"LOG_FORMAT": {
4747
value: "json"
4848
}
49+
"AUTH_PRIVATE_KEY": {
50+
value: "/certs/private.pem"
51+
}
52+
"AUTH_PUBLIC_KEY": {
53+
value: "/certs/public.pem"
54+
}
4955
"DB_INIT": {
5056
value: "true"
5157
}
@@ -96,6 +102,13 @@ project: {
96102
}
97103
}
98104

105+
mounts: {
106+
certs: {
107+
ref: secret: name: "certs"
108+
path: "/certs"
109+
}
110+
}
111+
99112
port: 8080
100113

101114
probes: {
@@ -110,6 +123,9 @@ project: {
110123
}
111124

112125
secrets: {
126+
certs: {
127+
ref: "foundry-api/certs"
128+
}
113129
db: {
114130
ref: "db/foundry"
115131
}

foundry/api/scripts/README.md

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# Foundry API Scripts
2+
3+
This directory contains scripts for managing the Foundry API infrastructure.
4+
5+
## init.sh
6+
7+
The `init.sh` script is responsible for generating and uploading authentication credentials to AWS Secrets Manager for the Foundry API and Operator services.
8+
9+
### What it does
10+
11+
1. **Generates certificates**: Uses Earthly to generate public and private key pairs for API authentication
12+
2. **Uploads certificates to AWS Secrets Manager**: Stores the certificates in the secret `FOUNDRY_API_CERTS_SECRET` with the following structure:
13+
```json
14+
{
15+
"public.pem": "-----BEGIN PUBLIC KEY-----...",
16+
"private.pem": "-----BEGIN PRIVATE KEY-----..."
17+
}
18+
```
19+
3. **Generates operator token**: Uses Earthly to generate a JWT token for the Foundry Operator
20+
4. **Uploads operator token to AWS Secrets Manager**: Stores the token in the secret `FOUNDRY_OPERATOR_TOKEN_SECRET` with the following structure:
21+
```json
22+
{
23+
"token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
24+
}
25+
```
26+
27+
### Prerequisites
28+
29+
- AWS CLI configured with appropriate permissions
30+
- Earthly installed and configured
31+
- `jq` command-line tool installed
32+
- AWS region set via `AWS_REGION` environment variable (defaults to `eu-central-1`)
33+
34+
### Environment Variables
35+
36+
The script requires the following environment variables to be set:
37+
38+
- `FOUNDRY_API_CERTS_SECRET` - AWS Secrets Manager path for API certificates
39+
- `FOUNDRY_OPERATOR_TOKEN_SECRET` - AWS Secrets Manager path for operator JWT token
40+
41+
You can set these variables in a `.env` file in the same directory as the script. Copy `env.example` to `.env` and update the values:
42+
43+
```bash
44+
cp env.example .env
45+
# Edit .env with your actual secret paths
46+
```
47+
48+
### Usage
49+
50+
```bash
51+
./init.sh
52+
```
53+
54+
### Security
55+
56+
- The script automatically cleans up local certificate and token files after upload
57+
- Sensitive files are removed from the local filesystem using a trap that runs on script exit
58+
- All credentials are stored securely in AWS Secrets Manager
59+
60+
### AWS Secrets Created/Updated
61+
62+
The script creates or updates secrets at the paths specified in your environment variables:
63+
64+
- `$FOUNDRY_API_CERTS_SECRET` - Contains API authentication certificates
65+
- `$FOUNDRY_OPERATOR_TOKEN_SECRET` - Contains operator JWT token

foundry/api/scripts/env.example

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# AWS Secrets Manager paths for Foundry API and Operator
2+
# Copy this file to .env and update with your actual secret paths
3+
4+
# Path for API certificates secret
5+
FOUNDRY_API_CERTS_SECRET="path/to/secret"
6+
7+
# Path for operator JWT token secret
8+
FOUNDRY_OPERATOR_TOKEN_SECRET="path/to/secret"

foundry/api/scripts/init.sh

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
#!/usr/bin/env bash
2+
3+
set -euo pipefail
4+
5+
# Disable AWS CLI pager to prevent interactive prompts
6+
export AWS_PAGER=""
7+
8+
# Source environment variables if .env file exists
9+
if [[ -f .env ]]; then
10+
echo ">>> Loading environment variables from .env"
11+
source .env
12+
fi
13+
14+
# Validate required environment variables
15+
if [[ -z "${FOUNDRY_API_CERTS_SECRET:-}" ]]; then
16+
echo "ERROR: FOUNDRY_API_CERTS_SECRET environment variable is required"
17+
echo "Please set it in a .env file or export it directly"
18+
exit 1
19+
fi
20+
21+
if [[ -z "${FOUNDRY_OPERATOR_TOKEN_SECRET:-}" ]]; then
22+
echo "ERROR: FOUNDRY_OPERATOR_TOKEN_SECRET environment variable is required"
23+
echo "Please set it in a .env file or export it directly"
24+
exit 1
25+
fi
26+
27+
cleanup() {
28+
echo ">>> Cleaning up certificates"
29+
rm -rf certs/
30+
rm -rf jwt/
31+
}
32+
trap cleanup EXIT
33+
34+
echo ">>> Generating certificates"
35+
earthly --config "" --artifact +certs/certs .
36+
37+
echo ">>> Uploading certificates to AWS Secrets Manager"
38+
PUBLIC_CERT=$(cat certs/public.pem)
39+
PRIVATE_CERT=$(cat certs/private.pem)
40+
41+
SECRET_JSON=$(jq -n \
42+
--arg public "$PUBLIC_CERT" \
43+
--arg private "$PRIVATE_CERT" \
44+
'{
45+
"public.pem": $public,
46+
"private.pem": $private
47+
}')
48+
49+
if aws secretsmanager describe-secret \
50+
--secret-id "$FOUNDRY_API_CERTS_SECRET" \
51+
--region "${AWS_REGION:-eu-central-1}" >/dev/null 2>&1; then
52+
echo ">>> Secret exists, updating..."
53+
aws secretsmanager put-secret-value \
54+
--secret-id "$FOUNDRY_API_CERTS_SECRET" \
55+
--secret-string "$SECRET_JSON" \
56+
--region "${AWS_REGION:-eu-central-1}"
57+
else
58+
echo ">>> Secret does not exist, creating..."
59+
aws secretsmanager create-secret \
60+
--name "$FOUNDRY_API_CERTS_SECRET" \
61+
--secret-string "$SECRET_JSON" \
62+
--region "${AWS_REGION:-eu-central-1}"
63+
fi
64+
65+
echo ">>> Generating operator token"
66+
earthly --config "" --artifact +jwt/jwt .
67+
68+
echo ">>> Uploading operator token to AWS Secrets Manager"
69+
JWT_TOKEN=$(cat jwt/token.txt)
70+
TOKEN_JSON=$(jq -n \
71+
--arg token "$JWT_TOKEN" \
72+
'{
73+
"token": $token
74+
}')
75+
76+
if aws secretsmanager describe-secret \
77+
--secret-id "$FOUNDRY_OPERATOR_TOKEN_SECRET" \
78+
--region "${AWS_REGION:-eu-central-1}" >/dev/null 2>&1; then
79+
echo ">>> Operator token secret exists, updating..."
80+
aws secretsmanager put-secret-value \
81+
--secret-id "$FOUNDRY_OPERATOR_TOKEN_SECRET" \
82+
--secret-string "$TOKEN_JSON" \
83+
--region "${AWS_REGION:-eu-central-1}"
84+
else
85+
echo ">>> Operator token secret does not exist, creating..."
86+
aws secretsmanager create-secret \
87+
--name "$FOUNDRY_OPERATOR_TOKEN_SECRET" \
88+
--secret-string "$TOKEN_JSON" \
89+
--region "${AWS_REGION:-eu-central-1}"
90+
fi
91+
92+
echo ">>> Certificates and operator token uploaded successfully to AWS Secrets Manager"

foundry/operator/Earthfile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,12 +92,12 @@ docker:
9292

9393
ARG TARGETOS
9494
ARG TARGETARCH
95-
ARG USERPLATFORM
95+
ARG TARGETPLATFORM
9696

9797
RUN apt-get update && apt-get install -y git
9898

9999
COPY \
100-
--platform=$USERPLATFORM \
100+
--platform=$TARGETPLATFORM \
101101
(+build/foundry-operator \
102102
--GOOS=$TARGETOS \
103103
--GOARCH=$TARGETARCH) foundry-operator

foundry/operator/blueprint.cue

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,10 @@ project: {
4848
ref: config: name: "config"
4949
path: "/config"
5050
}
51+
jwt: {
52+
ref: secret: name: "jwt"
53+
path: "/secret"
54+
}
5155
}
5256

5357
// probes: {
@@ -57,7 +61,10 @@ project: {
5761
}
5862

5963
configs: config: data: "operator.json": json.Marshal({
60-
api_url: "http://foundry-api:8080"
64+
api: {
65+
url: "http://foundry-api:8080"
66+
token_path: "/secret/token"
67+
}
6168
deployer: {
6269
git: {
6370
creds: {
@@ -93,6 +100,12 @@ project: {
93100
},
94101
]
95102
}
103+
104+
secrets: {
105+
jwt: {
106+
ref: "foundry-operator/token"
107+
}
108+
}
96109
}
97110
}
98111
}

foundry/operator/cmd/main.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"log/slog"
2424
"os"
2525
"path/filepath"
26+
"strings"
2627
"time"
2728

2829
// Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.)
@@ -235,7 +236,15 @@ func main() {
235236
os.Exit(1)
236237
}
237238

238-
apiClient := api.NewClient(cfg.ApiUrl, api.WithTimeout(10*time.Second))
239+
setupLog.Info("Reading JWT token from file", "path", cfg.Api.TokenPath)
240+
jwtToken, err := os.ReadFile(cfg.Api.TokenPath)
241+
if err != nil {
242+
setupLog.Error(err, "unable to read JWT token")
243+
os.Exit(1)
244+
}
245+
246+
jwtTokenStr := strings.TrimSpace(string(jwtToken))
247+
apiClient := api.NewClient(cfg.Api.Url, api.WithTimeout(10*time.Second), api.WithToken(jwtTokenStr))
239248
if err = (&controller.ReleaseDeploymentReconciler{
240249
Client: mgr.GetClient(),
241250
Config: cfg,

foundry/operator/pkg/config/config.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,16 @@ import (
1010

1111
// OperatorConfig is the configuration for the operator.
1212
type OperatorConfig struct {
13-
ApiUrl string `json:"api_url"`
13+
Api APIConfig `json:"api"`
1414
Deployer deployer.DeployerConfig `json:"deployer"`
1515
MaxAttempts int `json:"max_attempts"`
1616
}
1717

18+
type APIConfig struct {
19+
Url string `json:"url"`
20+
TokenPath string `json:"token_path"`
21+
}
22+
1823
// Load loads the operator configuration from the given path.
1924
func Load(path string) (OperatorConfig, error) {
2025
contents, err := os.ReadFile(path)

foundry/test/.justfile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ down:
66
kind delete cluster --name foundry
77

88
api:
9-
earthly ../api+docker
9+
earthly --config "" ../api+docker
1010
docker tag foundry-api:latest localhost:5001/foundry-api:latest
1111
docker push localhost:5001/foundry-api:latest
1212
kubectl delete deployment api
@@ -28,7 +28,7 @@ kind-up:
2828
./scripts/kind.sh
2929

3030
operator:
31-
earthly ../operator+docker
31+
earthly --config "" ../operator+docker
3232
docker tag foundry-operator:latest localhost:5001/foundry-operator:latest
3333
docker push localhost:5001/foundry-operator:latest
3434
kubectl delete deployment operator

0 commit comments

Comments
 (0)