mirror of
https://github.com/nexodus-io/nexodus.git
synced 2026-04-23 00:27:03 +08:00
Use go-oidc-agent for authentication (#204)
* Use go-oidc-agent for authentication Creates a "backend-for-frontend" using go-oidc-agent which now handles authentication on the clients behalf. This avoids us having to populate client-id/client-secret on every client since the backend now provides this information to the frontend. Signed-off-by: Dave Tucker <dave@dtucker.co.uk> * Use go-oidc-agent for cli Signed-off-by: Dave Tucker <dave@dtucker.co.uk> * deploy: Kustomize the kube deployment Signed-off-by: Dave Tucker <dave@dtucker.co.uk> * Move integration tests to use kind Signed-off-by: Dave Tucker <dave@dtucker.co.uk> * Fix GH workflow Signed-off-by: Dave Tucker <dave@dtucker.co.uk> * tests: Remove old tests Signed-off-by: Dave Tucker <dave@dtucker.co.uk> * client: Fix missing id_token in Device Flow Signed-off-by: Dave Tucker <dave@dtucker.co.uk>
This commit is contained in:
+1
-28
@@ -1,30 +1,3 @@
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Test binary, built with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
# Built binaries
|
||||
/apex
|
||||
apex-amd64-darwin
|
||||
apex-amd64-linux
|
||||
/controller
|
||||
|
||||
# Ops files
|
||||
ops/
|
||||
tests/
|
||||
docs/
|
||||
**/peer-inventory/*
|
||||
**/endpoints.toml
|
||||
**/default-ipam.json
|
||||
|
||||
ui/node_modules
|
||||
|
||||
docker-compose.yml
|
||||
Makefile
|
||||
|
||||
@@ -1,18 +1,13 @@
|
||||
CONTROLLER_DB_USER=controller
|
||||
CONTROLLER_DB_PASSWORD=floofykittens
|
||||
CONTROLLER_DB_HOST=controller-db
|
||||
APISERVER_DB_HOST=apiserver-db
|
||||
APISERVER_DB_USER=apiserver
|
||||
APISERVER_DB_PASSWORD=floofykittens
|
||||
IPAM_DB_USER=ipam
|
||||
IPAM_DB_PASSWORD=floofykittens
|
||||
IPAM_DB_HOST=ipam-db
|
||||
IPAM_ADDRESS=http://ipam:9090
|
||||
KEYCLOAK_DB_USER=keycloak
|
||||
KEYCLOAK_DB_PASSWORD=floofykittens
|
||||
KEYCLOAK_DB_HOST=keycloak-db
|
||||
KEYCLOAK_ADMIN_USER=admin
|
||||
KEYCLOAK_ADMIN_PASSWORD=floofykittens
|
||||
KEYCLOAK_URL=http://localhost:8080/auth
|
||||
UI_URL=http://localhost:8080
|
||||
VITE_KEYCLOAK_URL=/auth
|
||||
VITE_KEYCLOAK_REALM=controller
|
||||
VITE_KEYCLOAK_CLIENT_ID=front-controller
|
||||
VITE_CONTROLLER_URL=/api
|
||||
UI_URL=http://apex.local
|
||||
API_URL=http://api.apex.local
|
||||
APEX_OIDC_URL=http://auth.apex.local
|
||||
APEX_OIDC_CLIENT_ID=apex-web
|
||||
APEX_OIDC_CLIENT_SECRET=dhEN2dsqyUg5qmaDAdqi4CmH
|
||||
APEX_OIDC_CLIENT_ID_CLI=apex-cli
|
||||
|
||||
@@ -12,7 +12,6 @@ on:
|
||||
- '**/*.md'
|
||||
- '**/*.gitignore'
|
||||
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -88,13 +87,6 @@ jobs:
|
||||
name: e2e-integration
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
deployment: ["wireguard", "fedora-wireguard"]
|
||||
env:
|
||||
JOB_NAME: "apex-e2e-${{ matrix.deployment }}"
|
||||
DEPLOYMENT: ${{ matrix.deployment }}
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v2
|
||||
@@ -104,36 +96,16 @@ jobs:
|
||||
with:
|
||||
go-version: 1.19
|
||||
|
||||
- name: Add hosts to /etc/hosts
|
||||
run: |
|
||||
echo "127.0.0.1 auth.apex.local api.apex.local apex.local" | sudo tee -a /etc/hosts
|
||||
|
||||
- name: Run e2e
|
||||
run: |
|
||||
make OS_IMAGE=quay.io/networkstatic/${{ matrix.DEPLOYMENT }} e2e
|
||||
|
||||
- name: Archive logs
|
||||
uses: actions/upload-artifact@v3
|
||||
if: always()
|
||||
with:
|
||||
name: ${{ matrix.deployment }}-logs
|
||||
path: docker-compose.log
|
||||
|
||||
go-e2e:
|
||||
needs: lint
|
||||
name: go-e2e-integration
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
fail-fast: false
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: 1.19
|
||||
- name: Run go-e2e
|
||||
run: |
|
||||
make go-e2e
|
||||
make e2e
|
||||
|
||||
deploy:
|
||||
needs: [ "build", "e2e", "go-e2e" ]
|
||||
needs: [ "build", "e2e" ]
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: read
|
||||
|
||||
@@ -7,10 +7,8 @@ on:
|
||||
env:
|
||||
REGISTRY: quay.io
|
||||
REPOSITORY: apex
|
||||
CONTROLLER_IMAGE_NAME: controller
|
||||
CONTROLLER_UI_IMAGE_NAME: controller-ui
|
||||
APEX_IMAGE_NAME: apex
|
||||
KEYCLOAK_IMAGE_NAME: keycloak
|
||||
APISERVER_IMAGE_NAME: apiserver
|
||||
APISERVER_UI_IMAGE_NAME: frontend
|
||||
IMAGE_TAG: latest
|
||||
|
||||
jobs:
|
||||
@@ -28,102 +26,35 @@ jobs:
|
||||
username: ${{ secrets.QUAY_ROBOT_USERNAME }}
|
||||
password: ${{ secrets.QUAY_ROBOT_PASSWORD }}
|
||||
|
||||
- name: Extract metadata (tags, labels) for apex controller image
|
||||
id: meta-controller
|
||||
uses: docker/metadata-action@v3.6.2
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.REPOSITORY }}/${{ env.CONTROLLER_IMAGE_NAME }}
|
||||
tags: |
|
||||
type=raw,value=${{ env.IMAGE_TAG }}
|
||||
|
||||
- name: Build controller image
|
||||
id: build-controller
|
||||
- name: Build apiserver image
|
||||
id: build-apiserver
|
||||
uses: redhat-actions/buildah-build@v2
|
||||
with:
|
||||
image: controller
|
||||
tags: ${{ steps.meta-controller.outputs.tags }}
|
||||
labels: ${{ steps.meta-controller.outputs.labels }}
|
||||
image: apiserver
|
||||
tags: latest ${{ github.sha }} ${{github.ref_name}}
|
||||
containerfiles: |
|
||||
./Containerfile.controller
|
||||
./Containerfile.apiserver
|
||||
|
||||
- name: Push controller to quay.io/
|
||||
- name: Push apiserver to quay.io/
|
||||
uses: redhat-actions/push-to-registry@v2
|
||||
with:
|
||||
image: ${{ steps.build-controller.outputs.image }}
|
||||
tags: ${{ steps.build-controller.outputs.tags }}
|
||||
image: ${{ steps.build-apiserver.outputs.image }}
|
||||
tags: ${{ steps.build-apiserver.outputs.tags }}
|
||||
registry: ${{ env.REGISTRY }}/${{ env.REPOSITORY }}
|
||||
|
||||
- name: Extract metadata (tags, labels) for controller-ui image
|
||||
id: meta-controller-ui
|
||||
uses: docker/metadata-action@v3.6.2
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.REPOSITORY }}/${{ env.CONTROLLER_UI_IMAGE_NAME }}
|
||||
tags: |
|
||||
type=raw,value=${{ env.IMAGE_TAG }}
|
||||
|
||||
- name: Build controller-ui image
|
||||
id: build-controller-ui
|
||||
- name: Build frontend image
|
||||
id: build-frontend
|
||||
uses: redhat-actions/buildah-build@v2
|
||||
with:
|
||||
image: controller-ui
|
||||
tags: ${{ steps.meta-controller-ui.outputs.tags }}
|
||||
labels: ${{ steps.meta-controller-ui.outputs.labels }}
|
||||
image: apiserver-ui
|
||||
tags: latest ${{ github.sha }} ${{github.ref_name}}
|
||||
labels: ${{ steps.meta-apiserver-ui.outputs.labels }}
|
||||
containerfiles: |
|
||||
./Containerfile.ui
|
||||
./Containerfile.frontend
|
||||
|
||||
- name: Push controller-ui to quay.io
|
||||
- name: Push frontend to quay.io
|
||||
uses: redhat-actions/push-to-registry@v2
|
||||
with:
|
||||
image: ${{ steps.build-controller-ui.outputs.image }}
|
||||
tags: ${{ steps.build-controller-ui.outputs.tags }}
|
||||
registry: ${{ env.REGISTRY }}/${{ env.REPOSITORY }}
|
||||
|
||||
- name: Extract metadata (tags, labels) for apex agent image
|
||||
id: meta-apex
|
||||
uses: docker/metadata-action@v3.6.2
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.REPOSITORY }}/${{ env.APEX_IMAGE_NAME }}
|
||||
tags: |
|
||||
type=raw,value=${{ env.IMAGE_TAG }}
|
||||
|
||||
- name: Build apex image
|
||||
id: build-apex
|
||||
uses: redhat-actions/buildah-build@v2
|
||||
with:
|
||||
image: apex
|
||||
tags: ${{ steps.meta-apex.outputs.tags }}
|
||||
labels: ${{ steps.meta-apex.outputs.labels }}
|
||||
containerfiles: |
|
||||
./Containerfile.apex
|
||||
|
||||
- name: Push apex to quay.io/
|
||||
uses: redhat-actions/push-to-registry@v2
|
||||
with:
|
||||
image: ${{ steps.build-apex.outputs.image }}
|
||||
tags: ${{ steps.build-apex.outputs.tags }}
|
||||
registry: ${{ env.REGISTRY }}/${{ env.REPOSITORY }}
|
||||
|
||||
- name: Extract metadata (tags, labels) for keycloak image
|
||||
id: meta-keycloak
|
||||
uses: docker/metadata-action@v3.6.2
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.REPOSITORY }}/${{ env.KEYCLOAK_IMAGE_NAME }}
|
||||
tags: |
|
||||
type=raw,value=${{ env.IMAGE_TAG }}
|
||||
|
||||
- name: Build keycloak image
|
||||
id: build-keycloak
|
||||
uses: redhat-actions/buildah-build@v2
|
||||
with:
|
||||
image: keycloak
|
||||
tags: ${{ steps.meta-keycloak.outputs.tags }}
|
||||
labels: ${{ steps.meta-keycloak.outputs.labels }}
|
||||
containerfiles: |
|
||||
./Containerfile.keycloak
|
||||
|
||||
- name: Push keycloak to quay.io/
|
||||
uses: redhat-actions/push-to-registry@v2
|
||||
with:
|
||||
image: ${{ steps.build-keycloak.outputs.image }}
|
||||
tags: ${{ steps.build-keycloak.outputs.tags }}
|
||||
image: ${{ steps.build-frontend.outputs.image }}
|
||||
tags: ${{ steps.build-frontend.outputs.tags }}
|
||||
registry: ${{ env.REGISTRY }}/${{ env.REPOSITORY }}
|
||||
|
||||
@@ -28,3 +28,4 @@ docker-compose.log
|
||||
|
||||
# config
|
||||
.env.*
|
||||
coredns.yaml
|
||||
|
||||
+1
-1
@@ -13,4 +13,4 @@
|
||||
]
|
||||
regexes = [
|
||||
# '''custom-regexes-to-ignore-here''',
|
||||
]
|
||||
]
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
FROM docker.io/library/golang:1.19-alpine as build
|
||||
|
||||
WORKDIR /src
|
||||
COPY go.mod .
|
||||
COPY go.sum .
|
||||
RUN go mod download
|
||||
|
||||
COPY . .
|
||||
RUN CGO_ENABLED=0 go build \
|
||||
-ldflags="-extldflags=-static" \
|
||||
-o apex ./cmd/apex
|
||||
|
||||
FROM docker.io/library/alpine:3.16
|
||||
|
||||
RUN apk add --no-cache wireguard-tools iputils
|
||||
COPY --from=build /src/apex /bin/apex
|
||||
EXPOSE 8080
|
||||
ENTRYPOINT [ "/apex" ]
|
||||
@@ -8,10 +8,10 @@ RUN go mod download
|
||||
COPY . .
|
||||
RUN CGO_ENABLED=0 go build \
|
||||
-ldflags="-extldflags=-static" \
|
||||
-o controller ./cmd/apexcontroller
|
||||
-o apiserver ./cmd/apiserver
|
||||
|
||||
FROM registry.access.redhat.com/ubi8/ubi
|
||||
|
||||
COPY --from=build /src/controller /controller
|
||||
COPY --from=build /src/apiserver /apiserver
|
||||
EXPOSE 8080
|
||||
ENTRYPOINT [ "/controller" ]
|
||||
ENTRYPOINT [ "/apiserver" ]
|
||||
@@ -6,11 +6,6 @@ COPY ui/package.json .
|
||||
COPY ui/yarn.lock .
|
||||
RUN yarn install
|
||||
|
||||
ARG VITE_KEYCLOAK_URL=/auth
|
||||
ARG VITE_KEYCLOAK_REALM=controller
|
||||
ARG VITE_KEYCLOAK_CLIENT_ID=front-controller
|
||||
ARG VITE_CONTROLLER_URL=/api
|
||||
|
||||
COPY ui .
|
||||
RUN yarn build
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
FROM quay.io/keycloak/keycloak:latest as builder
|
||||
|
||||
ENV KC_HEALTH_ENABLED=true
|
||||
ENV KC_METRICS_ENABLED=true
|
||||
ENV KC_HTTP_RELATIVE_PATH=/auth
|
||||
ENV KC_DB=postgres
|
||||
|
||||
RUN /opt/keycloak/bin/kc.sh build
|
||||
|
||||
FROM quay.io/keycloak/keycloak:latest
|
||||
COPY --from=builder /opt/keycloak/ /opt/keycloak/
|
||||
COPY ./hack/controller-realm.json /opt/keycloak/data/import/controller-realm.json
|
||||
ENTRYPOINT ["/opt/keycloak/bin/kc.sh"]
|
||||
@@ -1,5 +1,5 @@
|
||||
.PHONY: all
|
||||
all: go-lint apex controller
|
||||
all: go-lint apex apiserver
|
||||
|
||||
.PHONY: apex
|
||||
apex: dist/apex dist/apex-linux-arm dist/apex-linux-amd64 dist/apex-darwin-amd64 dist/apex-darwin-arm64 dist/apex-windows-amd64
|
||||
@@ -8,7 +8,7 @@ COMMON_DEPS=$(wildcard ./internal/**/*.go) go.sum go.mod
|
||||
|
||||
APEX_DEPS=$(COMMON_DEPS) $(wildcard cmd/apex/*.go)
|
||||
|
||||
CONTROLLER_DEPS=$(COMMON_DEPS) $(wildcard cmd/apexcontroller/*.go)
|
||||
TAG=$(shell git rev-parse HEAD)
|
||||
|
||||
dist:
|
||||
mkdir -p $@
|
||||
@@ -35,23 +35,8 @@ dist/apex-windows-amd64: $(APEX_DEPS) | dist
|
||||
clean:
|
||||
rm -rf dist
|
||||
|
||||
.PHONY: controller
|
||||
controller: dist/apexcontroller dist/apexcontroller-linux-amd64 dist/apexcontroller-darwin-amd64 dist/apexcontroller-darwin-arm64
|
||||
|
||||
dist/apexcontroller: $(CONTROLLER_DEPS) | dist
|
||||
CGO_ENABLED=0 go build -o $@ ./cmd/apexcontroller
|
||||
|
||||
dist/apexcontroller-linux-amd64: $(CONTROLLER_DEPS) | dist
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o $@ ./cmd/apexcontroller
|
||||
|
||||
dist/apexcontroller-darwin-amd64: $(CONTROLLER_DEPS) | dist
|
||||
CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -o $@ ./cmd/apexcontroller
|
||||
|
||||
dist/apexcontroller-darwin-arm64: $(CONTROLLER_DEPS) | dist
|
||||
CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -o $@ ./cmd/apexcontroller
|
||||
|
||||
.PHONY: go-lint
|
||||
go-lint: $(APEX_DEPS) $(CONTROLLER_DEPS)
|
||||
go-lint: $(APEX_DEPS) $(APISERVER_DEPS)
|
||||
@if ! which golangci-lint 2>&1 >/dev/null; then \
|
||||
echo "Please install golangci-lint." ; \
|
||||
echo "See: https://golangci-lint.run/usage/install/#local-installation" ; \
|
||||
@@ -61,7 +46,7 @@ go-lint: $(APEX_DEPS) $(CONTROLLER_DEPS)
|
||||
|
||||
.PHONY: gen-docs
|
||||
gen-docs:
|
||||
swag init -g ./cmd/apexcontroller/main.go -o ./internal/docs
|
||||
swag init -g ./cmd/apiserver/main.go -o ./internal/docs
|
||||
|
||||
.PHONY: test-images
|
||||
test-images:
|
||||
@@ -71,19 +56,19 @@ test-images:
|
||||
|
||||
OS_IMAGE?="quay.io/apex/test:fedora"
|
||||
|
||||
# Runs the CI e2e tests used in github actions
|
||||
.PHONY: e2e
|
||||
e2e: dist/apex
|
||||
docker compose build
|
||||
./tests/e2e-scripts/init-containers.sh -o $(OS_IMAGE)
|
||||
|
||||
.PHONY: e2e
|
||||
go-e2e: dist/apex test-images
|
||||
docker compose up --build -d
|
||||
go test -v --tags=integration ./integration-tests/...
|
||||
e2e: dist/apex test-images
|
||||
./hack/run_e2e.sh
|
||||
|
||||
.PHONY: recompose
|
||||
recompose: dist/apex
|
||||
docker-compose down
|
||||
docker-compose build
|
||||
docker-compose up -d
|
||||
|
||||
.PHONY: images
|
||||
images:
|
||||
docker build -f Containerfile.apiserver -t quay.io/apex/apiserver:$(TAG) .
|
||||
docker build -f Containerfile.frontend -t quay.io/apex/frontend:$(TAG) .
|
||||
docker tag quay.io/apex/apiserver:$(TAG) quay.io/apex/apiserver:latest
|
||||
docker tag quay.io/apex/frontend:$(TAG) quay.io/apex/frontend:latest
|
||||
|
||||
@@ -1,118 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
"github.com/redhat-et/apex/internal/database"
|
||||
"github.com/redhat-et/apex/internal/handlers"
|
||||
"github.com/redhat-et/apex/internal/ipam"
|
||||
"github.com/redhat-et/apex/internal/routers"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
const (
|
||||
acLogEnv = "APEX_CONTROLLER_LOGLEVEL"
|
||||
)
|
||||
|
||||
// @title Apex API
|
||||
// @version 1.0
|
||||
// @description This is the APEX API Server.
|
||||
|
||||
// @contact.name The Apex Authors
|
||||
// @contact.url https://github.com/redhat-et/apex/issues
|
||||
|
||||
// @license.name Apache 2.0
|
||||
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
|
||||
// @securitydefinitions.oauth2.implicit OAuth2Implicit
|
||||
// @authorizationurl /auth/realms/controller/protocol/openid-connect/auth
|
||||
// @scope.admin Grants read and write access to administrative information
|
||||
// @scope.user Grants read and write access to resources owned by this user
|
||||
|
||||
// @BasePath /api
|
||||
func main() {
|
||||
// set the log level
|
||||
env := os.Getenv(acLogEnv)
|
||||
if env == "debug" {
|
||||
log.SetLevel(log.DebugLevel)
|
||||
}
|
||||
|
||||
app := &cli.App{
|
||||
Name: "apex-controller",
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "keycloak-address",
|
||||
Value: "keycloak.apex.svc.cluster.local",
|
||||
Usage: "address of keycloak service",
|
||||
Required: false,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "db-address",
|
||||
Value: "",
|
||||
Usage: "address of db",
|
||||
Required: true,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "db-password",
|
||||
Value: "",
|
||||
Usage: "password of db",
|
||||
Required: true,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "ipam-address",
|
||||
Value: "",
|
||||
Usage: "address of ipam grpc service",
|
||||
Required: true,
|
||||
},
|
||||
},
|
||||
Action: func(cCtx *cli.Context) error {
|
||||
db, err := database.NewDatabase(
|
||||
cCtx.String("db-address"),
|
||||
"controller",
|
||||
cCtx.String("db-password"),
|
||||
"controller",
|
||||
5432,
|
||||
"disable",
|
||||
)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
ipam := ipam.NewIPAM(cCtx.String("ipam-address"))
|
||||
|
||||
api, err := handlers.NewAPI(cCtx.Context, db, ipam)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
router, err := routers.NewRouter(api, cCtx.String("keycloak-address"))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
server := &http.Server{
|
||||
Addr: "0.0.0.0:8080",
|
||||
Handler: router,
|
||||
}
|
||||
|
||||
go func() {
|
||||
if err = server.ListenAndServe(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}()
|
||||
|
||||
ch := make(chan os.Signal, 1)
|
||||
signal.Notify(ch, syscall.SIGTERM, syscall.SIGQUIT, syscall.SIGINT)
|
||||
<-ch
|
||||
|
||||
return server.Close()
|
||||
},
|
||||
}
|
||||
if err := app.Run(os.Args); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/redhat-et/apex/internal/client"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := &cli.App{
|
||||
Name: "apexctl",
|
||||
Flags: []cli.Flag{
|
||||
&cli.BoolFlag{
|
||||
Name: "debug",
|
||||
Value: false,
|
||||
Usage: "enable debug logging",
|
||||
EnvVars: []string{"APEX_DEBUG"},
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "host",
|
||||
Value: "http://api.apex.local",
|
||||
Usage: "api server",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "username",
|
||||
Required: true,
|
||||
Usage: "username",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "password",
|
||||
Required: true,
|
||||
Usage: "password",
|
||||
},
|
||||
},
|
||||
Commands: []*cli.Command{
|
||||
{
|
||||
Name: "zone",
|
||||
Usage: "commands relating to zones",
|
||||
Subcommands: []*cli.Command{
|
||||
{
|
||||
Name: "list",
|
||||
Usage: "list zones",
|
||||
Action: func(cCtx *cli.Context) error {
|
||||
c, err := client.NewClient(cCtx.Context,
|
||||
cCtx.String("host"),
|
||||
client.WithPasswordGrant(
|
||||
cCtx.String("username"),
|
||||
cCtx.String("password"),
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
res, err := c.ListZones()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Printf("%+v", res)
|
||||
return nil
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "create",
|
||||
Usage: "create a zones",
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "name",
|
||||
Required: true,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "description",
|
||||
Required: true,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "cidr",
|
||||
Required: true,
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "hub-zone",
|
||||
Value: false,
|
||||
},
|
||||
},
|
||||
Action: func(cCtx *cli.Context) error {
|
||||
c, err := client.NewClient(cCtx.Context,
|
||||
cCtx.String("host"),
|
||||
client.WithPasswordGrant(
|
||||
cCtx.String("username"),
|
||||
cCtx.String("password"),
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
res, err := c.CreateZone(
|
||||
cCtx.String("name"),
|
||||
cCtx.String("description"),
|
||||
cCtx.String("cidr"),
|
||||
cCtx.Bool("hub-zone"),
|
||||
)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Printf("%+v", res)
|
||||
return nil
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
if err := app.Run(os.Args); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
"github.com/redhat-et/apex/internal/database"
|
||||
"github.com/redhat-et/apex/internal/handlers"
|
||||
"github.com/redhat-et/apex/internal/ipam"
|
||||
"github.com/redhat-et/apex/internal/routers"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// @title Apex API
|
||||
// @version 1.0
|
||||
// @description This is the APEX API Server.
|
||||
|
||||
// @contact.name The Apex Authors
|
||||
// @contact.url https://github.com/redhat-et/apex/issues
|
||||
|
||||
// @license.name Apache 2.0
|
||||
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
|
||||
// @securitydefinitions.oauth2.implicit OAuth2Implicit
|
||||
// @scope.admin Grants read and write access to administrative information
|
||||
// @scope.user Grants read and write access to resources owned by this user
|
||||
|
||||
// @BasePath /api
|
||||
func main() {
|
||||
app := &cli.App{
|
||||
Name: "apex-controller",
|
||||
Flags: []cli.Flag{
|
||||
&cli.BoolFlag{
|
||||
Name: "debug",
|
||||
Value: false,
|
||||
Usage: "enable debug logging",
|
||||
EnvVars: []string{"APEX_DEBUG"},
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "oidc-url",
|
||||
Value: "http://keycloak:8080/realms/apex",
|
||||
Usage: "address of oidc provider",
|
||||
EnvVars: []string{"APEX_OIDC_URL"},
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "oidc-client-id-web",
|
||||
Value: "apex-web",
|
||||
Usage: "OIDC client id for web",
|
||||
EnvVars: []string{"APEX_OIDC_CLIENT_ID_WEB"},
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "oidc-client-id-cli",
|
||||
Value: "apex-web",
|
||||
Usage: "OIDC client id for cli",
|
||||
EnvVars: []string{"APEX_OIDC_CLIENT_ID_CLI"},
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "db-host",
|
||||
Value: "apiserver-db",
|
||||
Usage: "db host",
|
||||
EnvVars: []string{"APEX_DB_HOST"},
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "db-port",
|
||||
Value: "5432",
|
||||
Usage: "db port",
|
||||
EnvVars: []string{"APEX_DB_PORT"},
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "db-user",
|
||||
Value: "apiserver",
|
||||
Usage: "db user",
|
||||
EnvVars: []string{"APEX_DB_USER"},
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "db-password",
|
||||
Value: "secret",
|
||||
Usage: "db password",
|
||||
EnvVars: []string{"APEX_DB_PASSWORD"},
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "db-name",
|
||||
Value: "apiserver",
|
||||
Usage: "db name",
|
||||
EnvVars: []string{"APEX_DB_NAME"},
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "ipam-address",
|
||||
Value: "ipam:9090",
|
||||
Usage: "address of ipam grpc service",
|
||||
EnvVars: []string{"APEX_IPAM_URL"},
|
||||
},
|
||||
},
|
||||
Action: func(cCtx *cli.Context) error {
|
||||
// set the log level
|
||||
if cCtx.Bool("debug") {
|
||||
log.SetLevel(log.DebugLevel)
|
||||
}
|
||||
|
||||
db, err := database.NewDatabase(
|
||||
cCtx.String("db-host"),
|
||||
cCtx.String("db-user"),
|
||||
cCtx.String("db-password"),
|
||||
cCtx.String("db-name"),
|
||||
cCtx.String("db-port"),
|
||||
"disable",
|
||||
)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
ipam := ipam.NewIPAM(cCtx.String("ipam-address"))
|
||||
|
||||
api, err := handlers.NewAPI(cCtx.Context, db, ipam)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
router, err := routers.NewAPIRouter(
|
||||
cCtx.Context,
|
||||
api,
|
||||
cCtx.String("oidc-client-id-web"),
|
||||
cCtx.String("oidc-client-id-cli"),
|
||||
cCtx.String("oidc-url"),
|
||||
)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
server := &http.Server{
|
||||
Addr: "0.0.0.0:8080",
|
||||
Handler: router,
|
||||
}
|
||||
|
||||
go func() {
|
||||
if err = server.ListenAndServe(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}()
|
||||
|
||||
ch := make(chan os.Signal, 1)
|
||||
signal.Notify(ch, syscall.SIGTERM, syscall.SIGQUIT, syscall.SIGINT)
|
||||
<-ch
|
||||
|
||||
return server.Close()
|
||||
},
|
||||
}
|
||||
if err := app.Run(os.Args); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: controller
|
||||
namespace: apex
|
||||
spec:
|
||||
rules:
|
||||
- http:
|
||||
paths:
|
||||
- pathType: Prefix
|
||||
path: "/api/"
|
||||
backend:
|
||||
service:
|
||||
name: controller
|
||||
port:
|
||||
number: 8080
|
||||
- pathType: Prefix
|
||||
path: "/auth/"
|
||||
backend:
|
||||
service:
|
||||
name: keycloak
|
||||
port:
|
||||
number: 8080
|
||||
- pathType: Prefix
|
||||
path: "/"
|
||||
backend:
|
||||
service:
|
||||
name: frontend
|
||||
port:
|
||||
number: 3000
|
||||
host: <HOST_DNS>
|
||||
@@ -1,410 +0,0 @@
|
||||
kind: PersistentVolumeClaim
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: keycloak-postgres-pv-claim
|
||||
namespace: apex
|
||||
labels:
|
||||
app.kubernetes.io/component: keycloak
|
||||
app.kubernetes.io/instance: keycloak
|
||||
app.kubernetes.io/name: keycloak
|
||||
app.kubernetes.io/part-of: apex
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce # Sets read and write access
|
||||
resources:
|
||||
requests:
|
||||
storage: 5Gi # Sets volume size
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: keycloak
|
||||
namespace: apex
|
||||
labels:
|
||||
app.kubernetes.io/component: keycloak
|
||||
app.kubernetes.io/instance: keycloak
|
||||
app.kubernetes.io/name: keycloak
|
||||
app.kubernetes.io/part-of: apex
|
||||
spec:
|
||||
selector:
|
||||
app.kubernetes.io/instance: keycloak
|
||||
app.kubernetes.io/name: keycloak
|
||||
ports:
|
||||
- name: http
|
||||
port: 8080
|
||||
targetPort: 8080
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: StatefulSet
|
||||
metadata:
|
||||
name: keycloak
|
||||
namespace: apex
|
||||
labels:
|
||||
app.kubernetes.io/component: keycloak
|
||||
app.kubernetes.io/instance: keycloak
|
||||
app.kubernetes.io/name: keycloak
|
||||
app.kubernetes.io/part-of: apex
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/component: keycloak
|
||||
app.kubernetes.io/instance: keycloak
|
||||
app.kubernetes.io/name: keycloak
|
||||
updateStrategy:
|
||||
type: RollingUpdate
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/component: keycloak
|
||||
app.kubernetes.io/instance: keycloak
|
||||
app.kubernetes.io/name: keycloak
|
||||
spec:
|
||||
containers:
|
||||
- name: keycloak-db
|
||||
image: quay.io/apex/postgres:latest
|
||||
imagePullPolicy: IfNotPresent
|
||||
env:
|
||||
- name: POSTGRES_PASSWORD
|
||||
value: floofykittens
|
||||
- name: POSTGRES_USER
|
||||
value: keycloak
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 200Mi
|
||||
limits:
|
||||
cpu: 100m
|
||||
memory: 200Mi
|
||||
volumeMounts:
|
||||
- mountPath: /var/lib/postgresql
|
||||
subPath: data
|
||||
name: postgresdb
|
||||
- name: keycloak
|
||||
image: quay.io/apex/keycloak:latest
|
||||
imagePullPolicy: Always
|
||||
args:
|
||||
- start
|
||||
- --optimized
|
||||
- --import-realm
|
||||
env:
|
||||
- name: KEYCLOAK_ADMIN
|
||||
value: admin
|
||||
- name: KEYCLOAK_ADMIN_PASSWORD
|
||||
value: floofykittens
|
||||
- name: KC_DB
|
||||
value: postgres
|
||||
- name: KC_DB_URL
|
||||
value: jdbc:postgresql://localhost/keycloak
|
||||
- name: KC_DB_USERNAME
|
||||
value: keycloak
|
||||
- name: KC_DB_PASSWORD
|
||||
value: floofykittens
|
||||
- name: UI_URL
|
||||
value: UI_URL_VALUE
|
||||
- name: KC_LOG_LEVEL
|
||||
value: info
|
||||
- name: KC_PROXY
|
||||
value: edge
|
||||
- name: KC_HOSTNAME_STRICT
|
||||
value: 'false'
|
||||
- name: KC_HOSTNAME_STRICT_BACKCHANNEL
|
||||
value: 'true'
|
||||
- name: KC_HOSTNAME_STRICT_HTTPS
|
||||
value: 'false'
|
||||
- name: KC_HTTP_ENABLED
|
||||
value: 'true'
|
||||
- name: PROXY_ADDRESS_FORWARDING
|
||||
value: "true"
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 8080
|
||||
- name: https
|
||||
containerPort: 8443
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 200Mi
|
||||
restartPolicy: Always
|
||||
volumes:
|
||||
- name: postgresdb
|
||||
persistentVolumeClaim:
|
||||
claimName: keycloak-postgres-pv-claim
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: ipam
|
||||
namespace: apex
|
||||
labels:
|
||||
app.kubernetes.io/component: ipam
|
||||
app.kubernetes.io/instance: ipam
|
||||
app.kubernetes.io/name: ipam
|
||||
app.kubernetes.io/part-of: apex
|
||||
spec:
|
||||
selector:
|
||||
app.kubernetes.io/component: ipam
|
||||
app.kubernetes.io/instance: ipam
|
||||
app.kubernetes.io/name: ipam
|
||||
ports:
|
||||
- port: 9090
|
||||
targetPort: 9090
|
||||
---
|
||||
kind: PersistentVolumeClaim
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: ipam-postgres-pv-claim
|
||||
namespace: apex
|
||||
labels:
|
||||
app.kubernetes.io/component: ipam
|
||||
app.kubernetes.io/instance: ipam
|
||||
app.kubernetes.io/name: ipam
|
||||
app.kubernetes.io/part-of: apex
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce # Sets read and write access
|
||||
resources:
|
||||
requests:
|
||||
storage: 5Gi # Sets volume size
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: StatefulSet
|
||||
metadata:
|
||||
name: ipam
|
||||
namespace: apex
|
||||
labels:
|
||||
app.kubernetes.io/component: ipam
|
||||
app.kubernetes.io/instance: ipam
|
||||
app.kubernetes.io/name: ipam
|
||||
app.kubernetes.io/part-of: apex
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/component: ipam
|
||||
app.kubernetes.io/instance: ipam
|
||||
app.kubernetes.io/name: ipam
|
||||
updateStrategy:
|
||||
type: RollingUpdate
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/component: ipam
|
||||
app.kubernetes.io/instance: ipam
|
||||
app.kubernetes.io/name: ipam
|
||||
spec:
|
||||
containers:
|
||||
- name: ipam-db
|
||||
image: quay.io/apex/postgres:latest
|
||||
imagePullPolicy: IfNotPresent
|
||||
env:
|
||||
- name: POSTGRES_PASSWORD
|
||||
value: floofykittens
|
||||
- name: POSTGRES_USER
|
||||
value: ipam
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 200Mi
|
||||
limits:
|
||||
cpu: 100m
|
||||
memory: 200Mi
|
||||
volumeMounts:
|
||||
- mountPath: /var/lib/postgresql
|
||||
subPath: data
|
||||
name: postgresdb
|
||||
- name: ipam
|
||||
image: ghcr.io/metal-stack/go-ipam
|
||||
imagePullPolicy: Always
|
||||
args:
|
||||
- --grpc-server-endpoint=0.0.0.0:9090
|
||||
- postgres
|
||||
- --host=localhost
|
||||
- --dbname=ipam
|
||||
- --user=ipam
|
||||
- --password=floofykittens
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 200Mi
|
||||
limits:
|
||||
cpu: 100m
|
||||
memory: 200Mi
|
||||
restartPolicy: Always
|
||||
volumes:
|
||||
- name: postgresdb
|
||||
persistentVolumeClaim:
|
||||
claimName: ipam-postgres-pv-claim
|
||||
---
|
||||
kind: PersistentVolumeClaim
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: controller-postgres-pv-claim
|
||||
namespace: apex
|
||||
labels:
|
||||
app.kubernetes.io/component: controller
|
||||
app.kubernetes.io/instance: controller
|
||||
app.kubernetes.io/name: controller
|
||||
app.kubernetes.io/part-of: apex
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce # Sets read and write access
|
||||
resources:
|
||||
requests:
|
||||
storage: 5Gi # Sets volume size
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: controller
|
||||
namespace: apex
|
||||
labels:
|
||||
app.kubernetes.io/component: controller
|
||||
app.kubernetes.io/instance: controller
|
||||
app.kubernetes.io/name: controller
|
||||
app.kubernetes.io/part-of: apex
|
||||
spec:
|
||||
selector:
|
||||
app.kubernetes.io/component: controller
|
||||
app.kubernetes.io/instance: controller
|
||||
app.kubernetes.io/name: controller
|
||||
ports:
|
||||
- port: 8080
|
||||
targetPort: 8080
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: StatefulSet
|
||||
metadata:
|
||||
name: controller
|
||||
namespace: apex
|
||||
labels:
|
||||
app.kubernetes.io/component: controller
|
||||
app.kubernetes.io/instance: controller
|
||||
app.kubernetes.io/name: controller
|
||||
app.kubernetes.io/part-of: apex
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/component: controller
|
||||
app.kubernetes.io/instance: controller
|
||||
app.kubernetes.io/name: controller
|
||||
updateStrategy:
|
||||
type: RollingUpdate
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/component: controller
|
||||
app.kubernetes.io/instance: controller
|
||||
app.kubernetes.io/name: controller
|
||||
spec:
|
||||
containers:
|
||||
- name: controller-db
|
||||
image: quay.io/apex/postgres:latest
|
||||
imagePullPolicy: IfNotPresent
|
||||
env:
|
||||
- name: POSTGRES_PASSWORD
|
||||
value: floofykittens
|
||||
- name: POSTGRES_USER
|
||||
value: controller
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 200Mi
|
||||
limits:
|
||||
cpu: 100m
|
||||
memory: 200Mi
|
||||
volumeMounts:
|
||||
- mountPath: /var/lib/postgresql
|
||||
subPath: data
|
||||
name: postgresdb
|
||||
- name: controller
|
||||
image: quay.io/apex/controller:latest
|
||||
imagePullPolicy: Always
|
||||
args:
|
||||
- --db-address=localhost
|
||||
- --db-password=floofykittens
|
||||
- --ipam-address=http://ipam.apex.svc.cluster.local:9090
|
||||
env:
|
||||
- name: APEX_CONTROLLER_LOGLEVEL
|
||||
value: debug
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 200Mi
|
||||
limits:
|
||||
cpu: 100m
|
||||
memory: 200Mi
|
||||
restartPolicy: Always
|
||||
volumes:
|
||||
- name: postgresdb
|
||||
persistentVolumeClaim:
|
||||
claimName: controller-postgres-pv-claim
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: frontend
|
||||
namespace: apex
|
||||
labels:
|
||||
app.kubernetes.io/component: frontend
|
||||
app.kubernetes.io/instance: frontend
|
||||
app.kubernetes.io/name: frontend
|
||||
app.kubernetes.io/part-of: apex
|
||||
spec:
|
||||
selector:
|
||||
app.kubernetes.io/component: frontend
|
||||
app.kubernetes.io/instance: frontend
|
||||
app.kubernetes.io/name: frontend
|
||||
ports:
|
||||
- port: 3000
|
||||
targetPort: 3000
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: frontend
|
||||
namespace: apex
|
||||
labels:
|
||||
app.kubernetes.io/component: frontend
|
||||
app.kubernetes.io/instance: frontend
|
||||
app.kubernetes.io/name: frontend
|
||||
app.kubernetes.io/part-of: apex
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/component: frontend
|
||||
app.kubernetes.io/instance: frontend
|
||||
app.kubernetes.io/name: frontend
|
||||
strategy:
|
||||
type: Recreate
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/component: frontend
|
||||
app.kubernetes.io/instance: frontend
|
||||
app.kubernetes.io/name: frontend
|
||||
spec:
|
||||
containers:
|
||||
- name: frontend
|
||||
image: quay.io/apex/controller-ui:latest
|
||||
imagePullPolicy: Always
|
||||
env:
|
||||
- name: VITE_KEYCLOAK_URL
|
||||
value: /auth
|
||||
- name: VITE_KEYCLOAK_REALM
|
||||
value: controller
|
||||
- name: VITE_KEYCLOAK_CLIENT_ID
|
||||
value: front-controller
|
||||
- name: VITE_CONTROLLER_URL
|
||||
value: /api
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 200Mi
|
||||
limits:
|
||||
cpu: 100m
|
||||
memory: 200Mi
|
||||
restartPolicy: Always
|
||||
---
|
||||
@@ -0,0 +1,40 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: apiproxy
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/component: apiproxy
|
||||
app.kubernetes.io/instance: apiproxy
|
||||
app.kubernetes.io/name: apiproxy
|
||||
strategy:
|
||||
type: Recreate
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/component: apiproxy
|
||||
app.kubernetes.io/instance: apiproxy
|
||||
app.kubernetes.io/name: apiproxy
|
||||
spec:
|
||||
containers:
|
||||
- name: caddy
|
||||
image: docker.io/library/caddy:2.6-alpine
|
||||
imagePullPolicy: IfNotPresent
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 200Mi
|
||||
limits:
|
||||
cpu: 100m
|
||||
memory: 200Mi
|
||||
volumeMounts:
|
||||
- name: config
|
||||
mountPath: /etc/caddy/Caddyfile
|
||||
subPath: Caddyfile
|
||||
restartPolicy: Always
|
||||
volumes:
|
||||
- name: config
|
||||
configMap:
|
||||
name: caddyfile
|
||||
@@ -0,0 +1,16 @@
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: apiproxy
|
||||
spec:
|
||||
rules:
|
||||
- host: <API_HOST>
|
||||
http:
|
||||
paths:
|
||||
- pathType: Prefix
|
||||
path: "/"
|
||||
backend:
|
||||
service:
|
||||
name: apiproxy
|
||||
port:
|
||||
number: 8080
|
||||
@@ -0,0 +1,10 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
resources:
|
||||
- deployment.yaml
|
||||
- ingress.yaml
|
||||
- service.yaml
|
||||
commonLabels:
|
||||
app.kubernetes.io/component: apiproxy
|
||||
app.kubernetes.io/instance: apiproxy
|
||||
app.kubernetes.io/name: apiproxy
|
||||
@@ -0,0 +1,12 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: apiproxy
|
||||
spec:
|
||||
selector:
|
||||
app.kubernetes.io/component: apiproxy
|
||||
app.kubernetes.io/instance: apiproxy
|
||||
app.kubernetes.io/name: apiproxy
|
||||
ports:
|
||||
- port: 8080
|
||||
targetPort: 8080
|
||||
@@ -0,0 +1,10 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
resources:
|
||||
- persistentvolumeclaim.yaml
|
||||
- service.yaml
|
||||
- statefulset.yaml
|
||||
commonLabels:
|
||||
app.kubernetes.io/component: apiserver
|
||||
app.kubernetes.io/instance: apiserver
|
||||
app.kubernetes.io/name: apiserver
|
||||
@@ -0,0 +1,10 @@
|
||||
kind: PersistentVolumeClaim
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: apiserver-postgres-pv-claim
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce # Sets read and write access
|
||||
resources:
|
||||
requests:
|
||||
storage: 5Gi # Sets volume size
|
||||
@@ -0,0 +1,12 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: apiserver
|
||||
spec:
|
||||
selector:
|
||||
app.kubernetes.io/component: apiserver
|
||||
app.kubernetes.io/instance: apiserver
|
||||
app.kubernetes.io/name: apiserver
|
||||
ports:
|
||||
- port: 8080
|
||||
targetPort: 8080
|
||||
@@ -0,0 +1,85 @@
|
||||
apiVersion: apps/v1
|
||||
kind: StatefulSet
|
||||
metadata:
|
||||
name: apiserver
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/component: apiserver
|
||||
app.kubernetes.io/instance: apiserver
|
||||
app.kubernetes.io/name: apiserver
|
||||
serviceName: "apiserver"
|
||||
updateStrategy:
|
||||
type: RollingUpdate
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/component: apiserver
|
||||
app.kubernetes.io/instance: apiserver
|
||||
app.kubernetes.io/name: apiserver
|
||||
spec:
|
||||
containers:
|
||||
- name: apiserver-db
|
||||
image: docker.io/library/postgres:15.1-alpine
|
||||
imagePullPolicy: IfNotPresent
|
||||
env:
|
||||
- name: POSTGRES_PASSWORD
|
||||
value: floofykittens
|
||||
- name: POSTGRES_USER
|
||||
value: apiserver
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 200Mi
|
||||
limits:
|
||||
cpu: 100m
|
||||
memory: 200Mi
|
||||
volumeMounts:
|
||||
- mountPath: /var/lib/postgresql
|
||||
subPath: data
|
||||
name: postgresdb
|
||||
- name: apiserver
|
||||
image: quay.io/apex/apiserver:latest
|
||||
imagePullPolicy: IfNotPresent
|
||||
env:
|
||||
- name: APEX_APISERVER_LOGLEVEL
|
||||
value: debug
|
||||
- name: APEX_DEBUG
|
||||
value: "1"
|
||||
- name: APEX_DB_HOST
|
||||
value: localhost
|
||||
- name: APEX_DB_NAME
|
||||
value: apiserver
|
||||
- name: APEX_DB_USER
|
||||
value: apiserver
|
||||
- name: APEX_DB_PASSWORD
|
||||
value: floofykittens
|
||||
- name: APEX_IPAM_URL
|
||||
value: http://ipam:9090
|
||||
- name: APEX_OIDC_URL
|
||||
value: <AUTH_HOST>
|
||||
- name: APEX_OIDC_CLIENT_ID_WEB
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: dex-secrets
|
||||
key: web-client-id
|
||||
optional: false
|
||||
- name: APEX_OIDC_CLIENT_ID_CLI
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: dex-secrets
|
||||
key: cli-client-id
|
||||
optional: false
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 200Mi
|
||||
limits:
|
||||
cpu: 100m
|
||||
memory: 200Mi
|
||||
restartPolicy: Always
|
||||
volumes:
|
||||
- name: postgresdb
|
||||
persistentVolumeClaim:
|
||||
claimName: apiserver-postgres-pv-claim
|
||||
@@ -0,0 +1,47 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: backend-cli
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/component: backend-cli
|
||||
app.kubernetes.io/instance: backend-cli
|
||||
app.kubernetes.io/name: backend-cli
|
||||
strategy:
|
||||
type: Recreate
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/component: backend-cli
|
||||
app.kubernetes.io/instance: backend-cli
|
||||
app.kubernetes.io/name: backend-cli
|
||||
spec:
|
||||
containers:
|
||||
- name: backend-cli
|
||||
image: quay.io/go-oidc-agent/go-oidc-agent:b98e1621e73404c5de21f47c7c5e6fab091e5d17
|
||||
imagePullPolicy: IfNotPresent
|
||||
env:
|
||||
- name: DEBUG
|
||||
value: "1"
|
||||
- name: OIDC_PROVIDER
|
||||
value: <AUTH_HOST>
|
||||
- name: OIDC_CLIENT_ID
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: dex-secrets
|
||||
key: cli-client-id
|
||||
optional: false
|
||||
- name: OIDC_FLOW
|
||||
value: device
|
||||
- name: BACKEND
|
||||
value: http://apiserver:8080
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 200Mi
|
||||
limits:
|
||||
cpu: 100m
|
||||
memory: 200Mi
|
||||
restartPolicy: Always
|
||||
@@ -0,0 +1,9 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
resources:
|
||||
- deployment.yaml
|
||||
- service.yaml
|
||||
commonLabels:
|
||||
app.kubernetes.io/component: backend-cli
|
||||
app.kubernetes.io/instance: backend-cli
|
||||
app.kubernetes.io/name: backend-cli
|
||||
@@ -0,0 +1,16 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: backend-cli
|
||||
labels:
|
||||
app.kubernetes.io/component: backend-cli
|
||||
app.kubernetes.io/instance: backend-cli
|
||||
app.kubernetes.io/name: backend-cli
|
||||
spec:
|
||||
selector:
|
||||
app.kubernetes.io/component: backend-cli
|
||||
app.kubernetes.io/instance: backend-cli
|
||||
app.kubernetes.io/name: backend-cli
|
||||
ports:
|
||||
- port: 8080
|
||||
targetPort: 8080
|
||||
@@ -0,0 +1,57 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: backend-web
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/component: backend-web
|
||||
app.kubernetes.io/instance: backend-web
|
||||
app.kubernetes.io/name: backend-web
|
||||
strategy:
|
||||
type: Recreate
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/component: backend-web
|
||||
app.kubernetes.io/instance: backend-web
|
||||
app.kubernetes.io/name: backend-web
|
||||
spec:
|
||||
containers:
|
||||
- name: backend-web
|
||||
image: quay.io/go-oidc-agent/go-oidc-agent:b98e1621e73404c5de21f47c7c5e6fab091e5d17
|
||||
imagePullPolicy: IfNotPresent
|
||||
env:
|
||||
- name: DEBUG
|
||||
value: "1"
|
||||
- name: OIDC_PROVIDER
|
||||
value: <AUTH_HOST>
|
||||
- name: OIDC_CLIENT_ID
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: dex-secrets
|
||||
key: web-client-id
|
||||
optional: false
|
||||
- name: OIDC_CLIENT_SECRET
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: dex-secrets
|
||||
key: web-client-secret
|
||||
optional: false
|
||||
- name: REDIRECT_URL
|
||||
value: <HOST>/#/login
|
||||
- name: ORIGINS
|
||||
value: <HOST>
|
||||
- name: DOMAIN
|
||||
value: <API_HOST>
|
||||
- name: BACKEND
|
||||
value: http://apiserver:8080
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 200Mi
|
||||
limits:
|
||||
cpu: 100m
|
||||
memory: 200Mi
|
||||
restartPolicy: Always
|
||||
@@ -0,0 +1,9 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
resources:
|
||||
- deployment.yaml
|
||||
- service.yaml
|
||||
commonLabels:
|
||||
app.kubernetes.io/component: backend-web
|
||||
app.kubernetes.io/instance: backend-web
|
||||
app.kubernetes.io/name: backend-web
|
||||
@@ -0,0 +1,16 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: backend-web
|
||||
labels:
|
||||
app.kubernetes.io/component: backend-web
|
||||
app.kubernetes.io/instance: backend-web
|
||||
app.kubernetes.io/name: backend-web
|
||||
spec:
|
||||
selector:
|
||||
app.kubernetes.io/component: backend-web
|
||||
app.kubernetes.io/instance: backend-web
|
||||
app.kubernetes.io/name: backend-web
|
||||
ports:
|
||||
- port: 8080
|
||||
targetPort: 8080
|
||||
@@ -0,0 +1,10 @@
|
||||
:8080 {
|
||||
@web {
|
||||
header User-Agent *Mozilla*
|
||||
}
|
||||
@notweb {
|
||||
not header User-Agent *Mozilla*
|
||||
}
|
||||
reverse_proxy @web backend-web:8080
|
||||
reverse_proxy @notweb backend-cli:8080
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
issuer: <AUTH_HOST>
|
||||
|
||||
storage:
|
||||
type: memory
|
||||
|
||||
web:
|
||||
http: 0.0.0.0:8080
|
||||
|
||||
expiry:
|
||||
deviceRequests: "5m"
|
||||
signingKeys: "6h"
|
||||
idTokens: "24h"
|
||||
refreshTokens:
|
||||
reuseInterval: "3s"
|
||||
validIfNotUsedFor: "2160h" # 90 days
|
||||
absoluteLifetime: "3960h" # 165 days
|
||||
|
||||
oauth2:
|
||||
responseTypes: ["code", "token", "id_token"]
|
||||
|
||||
staticClients:
|
||||
- idEnv: WEB_CLIENT_ID
|
||||
redirectURIs:
|
||||
- "<HOST>/#/login"
|
||||
name: "Apex Web Frontend"
|
||||
secretEnv: WEB_CLIENT_SECRET
|
||||
- idEnv: CLI_CLIENT_ID
|
||||
redirectURIs:
|
||||
- "/device/callback"
|
||||
name: "Apex CLI Frontend"
|
||||
public: true
|
||||
|
||||
enablePasswordDB: true
|
||||
|
||||
staticPasswords:
|
||||
- email: "admin@apex.local"
|
||||
hash: "$2y$10$BdXJbB0M2dsCzZQSYZBkT.GNaClwAuqG2Tv/qJUW8S4cy6AIIc.5a"
|
||||
username: "admin"
|
||||
userID: "10a31cfa-4181-4815-9aa2-f74e122412ee"
|
||||
- email: "kitteh1@apex.local"
|
||||
hash: "$2y$10$BdXJbB0M2dsCzZQSYZBkT.GNaClwAuqG2Tv/qJUW8S4cy6AIIc.5a"
|
||||
username: "kitteh1"
|
||||
userID: "189d32dc-0d64-42c1-b34d-ae2daea0cc22"
|
||||
- email: "kitteh2@apex.local"
|
||||
hash: "$2y$10$BdXJbB0M2dsCzZQSYZBkT.GNaClwAuqG2Tv/qJUW8S4cy6AIIc.5a"
|
||||
username: "kitteh2"
|
||||
userID: "05e5fdff-ed73-48fd-ad10-b9d457f1f1bb"
|
||||
- email: "kitteh3@apex.local"
|
||||
hash: "$2y$10$BdXJbB0M2dsCzZQSYZBkT.GNaClwAuqG2Tv/qJUW8S4cy6AIIc.5a"
|
||||
username: "kitteh3"
|
||||
userID: "32b869d6-f633-41be-ac72-40efe86d55f7"
|
||||
- email: "kitteh4@apex.local"
|
||||
hash: "$2y$10$BdXJbB0M2dsCzZQSYZBkT.GNaClwAuqG2Tv/qJUW8S4cy6AIIc.5a"
|
||||
username: "kitteh4"
|
||||
userID: "885c1d57-8ff9-406c-a15a-388b77bf7409"
|
||||
- email: "kitteh5@apex.local"
|
||||
hash: "$2y$10$BdXJbB0M2dsCzZQSYZBkT.GNaClwAuqG2Tv/qJUW8S4cy6AIIc.5a"
|
||||
username: "kitteh5"
|
||||
userID: "306d0921-44bd-45e8-a7de-f90dfb32abf7"
|
||||
@@ -0,0 +1,11 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
generatorOptions:
|
||||
disableNameSuffixHash: true
|
||||
configMapGenerator:
|
||||
- name: dex
|
||||
files:
|
||||
- files/config.yaml
|
||||
- name: caddyfile
|
||||
files:
|
||||
- files/Caddyfile
|
||||
@@ -0,0 +1,54 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dex
|
||||
labels:
|
||||
app.kubernetes.io/component: dex
|
||||
app.kubernetes.io/instance: dex
|
||||
app.kubernetes.io/name: dex
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/component: dex
|
||||
app.kubernetes.io/instance: dex
|
||||
app.kubernetes.io/name: dex
|
||||
strategy:
|
||||
type: Recreate
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/component: dex
|
||||
app.kubernetes.io/instance: dex
|
||||
app.kubernetes.io/name: dex
|
||||
spec:
|
||||
containers:
|
||||
- name: dex
|
||||
image: ghcr.io/dexidp/dex:v2.35.2
|
||||
imagePullPolicy: IfNotPresent
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 200Mi
|
||||
limits:
|
||||
cpu: 100m
|
||||
memory: 200Mi
|
||||
env:
|
||||
- name: WEB_CLIENT_SECRET
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: dex-secrets
|
||||
key: web-client-secret
|
||||
optional: false
|
||||
args:
|
||||
- dex
|
||||
- serve
|
||||
- /etc/dex/config.yaml
|
||||
volumeMounts:
|
||||
- name: config
|
||||
mountPath: /etc/dex
|
||||
restartPolicy: Always
|
||||
volumes:
|
||||
- name: config
|
||||
configMap:
|
||||
name: dex
|
||||
@@ -0,0 +1,16 @@
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: dex
|
||||
spec:
|
||||
rules:
|
||||
- host: <AUTH_HOST>
|
||||
http:
|
||||
paths:
|
||||
- pathType: Prefix
|
||||
path: "/"
|
||||
backend:
|
||||
service:
|
||||
name: dex
|
||||
port:
|
||||
number: 80
|
||||
@@ -0,0 +1,18 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
generatorOptions:
|
||||
disableNameSuffixHash: true
|
||||
resources:
|
||||
- deployment.yaml
|
||||
- ingress.yaml
|
||||
- service.yaml
|
||||
secretGenerator:
|
||||
- name: dex-secrets
|
||||
literals:
|
||||
- web-client-id=apex-web
|
||||
- web-client-secret=dhEN2dsqyUg5qmaDAdqi4CmH
|
||||
- cli-client-id=apex-cli
|
||||
commonLabels:
|
||||
app.kubernetes.io/component: dex
|
||||
app.kubernetes.io/instance: dex
|
||||
app.kubernetes.io/name: dex
|
||||
@@ -0,0 +1,12 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: dex
|
||||
spec:
|
||||
selector:
|
||||
app.kubernetes.io/instance: dex
|
||||
app.kubernetes.io/name: dex
|
||||
ports:
|
||||
- name: http
|
||||
port: 80
|
||||
targetPort: 8080
|
||||
@@ -0,0 +1,32 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: frontend
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/component: frontend
|
||||
app.kubernetes.io/instance: frontend
|
||||
app.kubernetes.io/name: frontend
|
||||
strategy:
|
||||
type: Recreate
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/component: frontend
|
||||
app.kubernetes.io/instance: frontend
|
||||
app.kubernetes.io/name: frontend
|
||||
spec:
|
||||
containers:
|
||||
- name: frontend
|
||||
image: quay.io/apex/frontend:latest
|
||||
imagePullPolicy: IfNotPresent
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 200Mi
|
||||
limits:
|
||||
cpu: 100m
|
||||
memory: 200Mi
|
||||
restartPolicy: Always
|
||||
@@ -0,0 +1,16 @@
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: frontend
|
||||
spec:
|
||||
rules:
|
||||
- host: <HOST>
|
||||
http:
|
||||
paths:
|
||||
- pathType: Prefix
|
||||
path: "/"
|
||||
backend:
|
||||
service:
|
||||
name: frontend
|
||||
port:
|
||||
number: 3000
|
||||
@@ -0,0 +1,10 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
resources:
|
||||
- deployment.yaml
|
||||
- ingress.yaml
|
||||
- service.yaml
|
||||
commonLabels:
|
||||
app.kubernetes.io/component: frontend
|
||||
app.kubernetes.io/instance: frontend
|
||||
app.kubernetes.io/name: frontend
|
||||
@@ -0,0 +1,12 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: frontend
|
||||
spec:
|
||||
selector:
|
||||
app.kubernetes.io/component: frontend
|
||||
app.kubernetes.io/instance: frontend
|
||||
app.kubernetes.io/name: frontend
|
||||
ports:
|
||||
- port: 3000
|
||||
targetPort: 3000
|
||||
@@ -0,0 +1,10 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
resources:
|
||||
- persistentvolumeclaim.yaml
|
||||
- service.yaml
|
||||
- statefulset.yaml
|
||||
commonLabels:
|
||||
app.kubernetes.io/component: ipam
|
||||
app.kubernetes.io/instance: ipam
|
||||
app.kubernetes.io/name: ipam
|
||||
@@ -0,0 +1,10 @@
|
||||
kind: PersistentVolumeClaim
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: ipam-postgres-pv-claim
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce # Sets read and write access
|
||||
resources:
|
||||
requests:
|
||||
storage: 5Gi # Sets volume size
|
||||
@@ -0,0 +1,12 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: ipam
|
||||
spec:
|
||||
selector:
|
||||
app.kubernetes.io/component: ipam
|
||||
app.kubernetes.io/instance: ipam
|
||||
app.kubernetes.io/name: ipam
|
||||
ports:
|
||||
- port: 9090
|
||||
targetPort: 9090
|
||||
@@ -0,0 +1,63 @@
|
||||
apiVersion: apps/v1
|
||||
kind: StatefulSet
|
||||
metadata:
|
||||
name: ipam
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/component: ipam
|
||||
app.kubernetes.io/instance: ipam
|
||||
app.kubernetes.io/name: ipam
|
||||
serviceName: "ipam"
|
||||
updateStrategy:
|
||||
type: RollingUpdate
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/component: ipam
|
||||
app.kubernetes.io/instance: ipam
|
||||
app.kubernetes.io/name: ipam
|
||||
spec:
|
||||
containers:
|
||||
- name: ipam-db
|
||||
image: docker.io/library/postgres:15.1-alpine
|
||||
imagePullPolicy: IfNotPresent
|
||||
env:
|
||||
- name: POSTGRES_PASSWORD
|
||||
value: floofykittens
|
||||
- name: POSTGRES_USER
|
||||
value: ipam
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 200Mi
|
||||
limits:
|
||||
cpu: 100m
|
||||
memory: 200Mi
|
||||
volumeMounts:
|
||||
- mountPath: /var/lib/postgresql
|
||||
subPath: data
|
||||
name: postgresdb
|
||||
- name: ipam
|
||||
image: ghcr.io/metal-stack/go-ipam:v1.11.3
|
||||
imagePullPolicy: IfNotPresent
|
||||
args:
|
||||
- --grpc-server-endpoint=0.0.0.0:9090
|
||||
- postgres
|
||||
- --host=localhost
|
||||
- --dbname=ipam
|
||||
- --user=ipam
|
||||
- --password=floofykittens
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 200Mi
|
||||
limits:
|
||||
cpu: 100m
|
||||
memory: 200Mi
|
||||
restartPolicy: Always
|
||||
volumes:
|
||||
- name: postgresdb
|
||||
persistentVolumeClaim:
|
||||
claimName: ipam-postgres-pv-claim
|
||||
@@ -0,0 +1,15 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
namespace: apex
|
||||
resources:
|
||||
- apiproxy
|
||||
- apiserver
|
||||
- backend-cli
|
||||
- backend-web
|
||||
# configmaps must be implemented in overlays
|
||||
# - configmaps
|
||||
- dex
|
||||
- frontend
|
||||
- ipam
|
||||
commonLabels:
|
||||
app.kubernetes.io/part-of: apex
|
||||
@@ -0,0 +1,16 @@
|
||||
kind: Cluster
|
||||
apiVersion: kind.x-k8s.io/v1alpha4
|
||||
name: apex-dev
|
||||
nodes:
|
||||
- role: control-plane
|
||||
image: kindest/node:v1.24.7@sha256:577c630ce8e509131eab1aea12c022190978dd2f745aac5eb1fe65c0807eb315
|
||||
kubeadmConfigPatches:
|
||||
- |
|
||||
kind: InitConfiguration
|
||||
nodeRegistration:
|
||||
kubeletExtraArgs:
|
||||
node-labels: "ingress-ready=true"
|
||||
extraPortMappings:
|
||||
- containerPort: 80
|
||||
hostPort: 80
|
||||
protocol: TCP
|
||||
@@ -0,0 +1,12 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: backend-web
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: backend-web
|
||||
env:
|
||||
- name: DOMAIN
|
||||
value: api.apex.local
|
||||
@@ -0,0 +1,3 @@
|
||||
- op: replace
|
||||
path: /spec/rules/0/host
|
||||
value: api.apex.local
|
||||
@@ -0,0 +1,38 @@
|
||||
apiVersion: apps/v1
|
||||
kind: StatefulSet
|
||||
metadata:
|
||||
name: apiserver
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: apiserver
|
||||
env:
|
||||
- name: APEX_OIDC_URL
|
||||
value: http://auth.apex.local
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: backend-cli
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: backend-cli
|
||||
env:
|
||||
- name: OIDC_PROVIDER
|
||||
value: http://auth.apex.local
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: backend-web
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: backend-web
|
||||
env:
|
||||
- name: OIDC_PROVIDER
|
||||
value: http://auth.apex.local
|
||||
@@ -0,0 +1,3 @@
|
||||
- op: replace
|
||||
path: /spec/rules/0/host
|
||||
value: auth.apex.local
|
||||
@@ -0,0 +1,10 @@
|
||||
:8080 {
|
||||
@web {
|
||||
header User-Agent *Mozilla*
|
||||
}
|
||||
@notweb {
|
||||
not header User-Agent *Mozilla*
|
||||
}
|
||||
reverse_proxy @web backend-web:8080
|
||||
reverse_proxy @notweb backend-cli:8080
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
issuer: http://auth.apex.local
|
||||
|
||||
storage:
|
||||
type: memory
|
||||
|
||||
web:
|
||||
http: 0.0.0.0:8080
|
||||
|
||||
expiry:
|
||||
deviceRequests: "5m"
|
||||
signingKeys: "6h"
|
||||
idTokens: "24h"
|
||||
refreshTokens:
|
||||
reuseInterval: "3s"
|
||||
validIfNotUsedFor: "2160h" # 90 days
|
||||
absoluteLifetime: "3960h" # 165 days
|
||||
|
||||
oauth2:
|
||||
responseTypes: ["code", "token", "id_token"]
|
||||
passwordConnector: local
|
||||
|
||||
staticClients:
|
||||
- id: apex-web
|
||||
redirectURIs:
|
||||
- 'http://apex.local/#/login'
|
||||
name: 'Apex Web Frontend'
|
||||
secretEnv: WEB_CLIENT_SECRET
|
||||
- id: apex-cli
|
||||
redirectURIs:
|
||||
- '/device/callback'
|
||||
name: 'Apex CLI Frontend'
|
||||
public: true
|
||||
|
||||
enablePasswordDB: true
|
||||
|
||||
staticPasswords:
|
||||
- email: "admin@apex.local"
|
||||
hash: "$2y$10$BdXJbB0M2dsCzZQSYZBkT.GNaClwAuqG2Tv/qJUW8S4cy6AIIc.5a"
|
||||
username: "admin"
|
||||
userID: "10a31cfa-4181-4815-9aa2-f74e122412ee"
|
||||
- email: "kitteh1@apex.local"
|
||||
hash: "$2y$10$BdXJbB0M2dsCzZQSYZBkT.GNaClwAuqG2Tv/qJUW8S4cy6AIIc.5a"
|
||||
username: "kitteh1"
|
||||
userID: "189d32dc-0d64-42c1-b34d-ae2daea0cc22"
|
||||
- email: "kitteh2@apex.local"
|
||||
hash: "$2y$10$BdXJbB0M2dsCzZQSYZBkT.GNaClwAuqG2Tv/qJUW8S4cy6AIIc.5a"
|
||||
username: "kitteh2"
|
||||
userID: "05e5fdff-ed73-48fd-ad10-b9d457f1f1bb"
|
||||
- email: "kitteh3@apex.local"
|
||||
hash: "$2y$10$BdXJbB0M2dsCzZQSYZBkT.GNaClwAuqG2Tv/qJUW8S4cy6AIIc.5a"
|
||||
username: "kitteh3"
|
||||
userID: "32b869d6-f633-41be-ac72-40efe86d55f7"
|
||||
- email: "kitteh4@apex.local"
|
||||
hash: "$2y$10$BdXJbB0M2dsCzZQSYZBkT.GNaClwAuqG2Tv/qJUW8S4cy6AIIc.5a"
|
||||
username: "kitteh4"
|
||||
userID: "885c1d57-8ff9-406c-a15a-388b77bf7409"
|
||||
- email: "kitteh5@apex.local"
|
||||
hash: "$2y$10$BdXJbB0M2dsCzZQSYZBkT.GNaClwAuqG2Tv/qJUW8S4cy6AIIc.5a"
|
||||
username: "kitteh5"
|
||||
userID: "306d0921-44bd-45e8-a7de-f90dfb32abf7"
|
||||
@@ -0,0 +1,12 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
namespace: apex
|
||||
generatorOptions:
|
||||
disableNameSuffixHash: true
|
||||
configMapGenerator:
|
||||
- name: dex
|
||||
files:
|
||||
- files/config.yaml
|
||||
- name: caddyfile
|
||||
files:
|
||||
- files/Caddyfile
|
||||
@@ -0,0 +1,14 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: backend-web
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: backend-web
|
||||
env:
|
||||
- name: REDIRECT_URL
|
||||
value: http://apex.local/#/login
|
||||
- name: ORIGINS
|
||||
value: http://apex.local
|
||||
@@ -0,0 +1,3 @@
|
||||
- op: replace
|
||||
path: /spec/rules/0/host
|
||||
value: apex.local
|
||||
@@ -0,0 +1,22 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
resources:
|
||||
- ../../base
|
||||
- configmaps
|
||||
patchesStrategicMerge:
|
||||
- api_host_env_patch.yaml
|
||||
- auth_host_env_patch.yaml
|
||||
- host_env_patch.yaml
|
||||
patchesJson6902:
|
||||
- target:
|
||||
kind: Ingress
|
||||
name: apiproxy
|
||||
path: api_host_ingress_patch.yaml
|
||||
- target:
|
||||
kind: Ingress
|
||||
name: dex
|
||||
path: auth_host_ingress_patch.yaml
|
||||
- target:
|
||||
kind: Ingress
|
||||
name: frontend
|
||||
path: host_ingress_patch.yaml
|
||||
@@ -1,130 +0,0 @@
|
||||
version: "3.7"
|
||||
|
||||
services:
|
||||
controller-db:
|
||||
image: docker.io/library/postgres
|
||||
environment:
|
||||
- POSTGRES_USER=${CONTROLLER_DB_USER}
|
||||
- POSTGRES_PASSWORD=${CONTROLLER_DB_PASSWORD}
|
||||
|
||||
ipam-db:
|
||||
image: docker.io/library/postgres
|
||||
environment:
|
||||
- POSTGRES_USER=${IPAM_DB_USER}
|
||||
- POSTGRES_PASSWORD=${IPAM_DB_PASSWORD}
|
||||
|
||||
ipam:
|
||||
image: ghcr.io/metal-stack/go-ipam
|
||||
depends_on:
|
||||
- ipam-db
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
command:
|
||||
- --grpc-server-endpoint=0.0.0.0:9090
|
||||
- postgres
|
||||
- --host=${IPAM_DB_HOST}
|
||||
- --dbname=ipam
|
||||
- --user=${IPAM_DB_USER}
|
||||
- --password=${IPAM_DB_PASSWORD}
|
||||
|
||||
controller:
|
||||
image: quay.io/apex/controller:latest
|
||||
depends_on:
|
||||
- controller-db
|
||||
- ipam
|
||||
- keycloak
|
||||
build:
|
||||
context: .
|
||||
dockerfile: ./Containerfile.controller
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
command:
|
||||
- "--keycloak-address=keycloak"
|
||||
- "--db-address=${CONTROLLER_DB_HOST}"
|
||||
- "--db-password=${CONTROLLER_DB_PASSWORD}"
|
||||
- "--ipam-address=${IPAM_ADDRESS}"
|
||||
environment:
|
||||
APEX_CONTROLLER_LOGLEVEL: debug
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.controller.rule=PathPrefix(`/api`)"
|
||||
- "traefik.http.routers.controller.entrypoints=web"
|
||||
- "traefik.http.services.my-controller.loadbalancer.server.port=8080"
|
||||
ui:
|
||||
image: quay.io/apex/controller-ui:latest
|
||||
depends_on:
|
||||
- controller
|
||||
- keycloak
|
||||
build:
|
||||
context: .
|
||||
dockerfile: ./Containerfile.ui
|
||||
args:
|
||||
- VITE_KEYCLOAK_URL=${VITE_KEYCLOAK_URL}
|
||||
- VITE_KEYCLOAK_REALM=${VITE_KEYCLOAK_REALM}
|
||||
- VITE_KEYCLOAK_CLIENT_ID=${VITE_KEYCLOAK_CLIENT_ID}
|
||||
- VITE_CONTROLLER_URL=${VITE_CONTROLLER_URL}
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.ui.rule=PathPrefix(`/`) && !PathPrefix(`/api`) && !PathPrefix(`/auth`)"
|
||||
- "traefik.http.routers.ui.entrypoints=web"
|
||||
- "traefik.http.services.ui.loadbalancer.server.port=3000"
|
||||
|
||||
keycloak-db:
|
||||
image: docker.io/library/postgres
|
||||
environment:
|
||||
- POSTGRES_USER=${KEYCLOAK_DB_USER}
|
||||
- POSTGRES_PASSWORD=${KEYCLOAK_DB_PASSWORD}
|
||||
|
||||
keycloak:
|
||||
image: quay.io/apex/keycloak:latest
|
||||
build:
|
||||
context: .
|
||||
dockerfile: ./Containerfile.keycloak
|
||||
depends_on:
|
||||
- keycloak-db
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
environment:
|
||||
- KEYCLOAK_ADMIN=${KEYCLOAK_ADMIN_USER}
|
||||
- KEYCLOAK_ADMIN_PASSWORD=${KEYCLOAK_ADMIN_PASSWORD}
|
||||
- KC_DB=postgres
|
||||
- KC_DB_URL=jdbc:postgresql://${KEYCLOAK_DB_HOST}/keycloak
|
||||
- KC_DB_USERNAME=${KEYCLOAK_DB_USER}
|
||||
- KC_DB_PASSWORD=${KEYCLOAK_DB_PASSWORD}
|
||||
- UI_URL=${UI_URL}
|
||||
- KC_PROXY=passthrough
|
||||
- KC_HOSTNAME_STRICT=false
|
||||
- KC_HOSTNAME_STRICT_BACKCHANNEL=true
|
||||
- KC_HOSTNAME_STRICT_HTTPS=false
|
||||
- KC_HTTP_ENABLED=true
|
||||
command:
|
||||
- start
|
||||
- --optimized
|
||||
- --import-realm
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.keycloak.rule=PathPrefix(`/auth`)"
|
||||
- "traefik.http.routers.keycloak.entrypoints=web"
|
||||
- "traefik.http.services.keycloak.loadbalancer.server.port=8080"
|
||||
|
||||
proxy:
|
||||
depends_on:
|
||||
- controller
|
||||
- keycloak
|
||||
- ui
|
||||
image: traefik:v2.9
|
||||
command:
|
||||
- --api.insecure=true
|
||||
- --providers.docker
|
||||
- --providers.docker.exposedbydefault=false
|
||||
- --entrypoints.web.address=:8080
|
||||
- --entrypoints.traefik.address=:8888
|
||||
ports:
|
||||
- "8080:8080"
|
||||
- "8888:8888"
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
|
||||
+27
-36
@@ -3,7 +3,6 @@
|
||||
- [Documentation](#documentation)
|
||||
- [Concepts](#concepts)
|
||||
- [Deploying the Apex Controller](#deploying-the-apex-controller)
|
||||
- [Using docker-compose](#using-docker-compose)
|
||||
- [Run on Kubernetes](#run-on-kubernetes)
|
||||
- [The Apex Agent](#the-apex-agent)
|
||||
- [Installing the Agent](#installing-the-agent)
|
||||
@@ -33,43 +32,35 @@
|
||||
|
||||
## Deploying the Apex Controller
|
||||
|
||||
### Using docker-compose
|
||||
|
||||
> **_NOTE:_** These instructions do not work with `podman-compose`. Instead of
|
||||
> fixing `podman` compatibility, we will be moving exclusively to kubernetes as
|
||||
> a local development platform using `kind` or `minikube`. Once those
|
||||
> instructions are ready, `docker-compose` support will be removed.
|
||||
|
||||
For development and testing purposes, the quickest way to run the controller stack is by using `docker-compose`.
|
||||
|
||||
First, to build all required container images:
|
||||
|
||||
```sh
|
||||
docker-compose build
|
||||
```
|
||||
|
||||
To bring up the stack:
|
||||
|
||||
```sh
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
To verify that everything has come up successfully:
|
||||
|
||||
```sh
|
||||
$ curl http://localhost:8080/api/health
|
||||
{"message":"ok"}
|
||||
```
|
||||
|
||||
To tear everything back down:
|
||||
|
||||
```sh
|
||||
docker-compose down
|
||||
```
|
||||
|
||||
### Run on Kubernetes
|
||||
|
||||
Coming soon ...
|
||||
#### Add required DNS entries
|
||||
|
||||
The development Apex stack requires 3 hostnames to be reachable:
|
||||
|
||||
- `auth.apex.local` - for the authentication service
|
||||
- `api.apex.local` - for the backend apis
|
||||
- `apex.local` - for the frontend
|
||||
|
||||
To add these on your own machine:
|
||||
```console
|
||||
echo "127.0.0.1 auth.apex.local api.apex.local apex.local" | sudo tee -a /etc/hosts
|
||||
```
|
||||
|
||||
#### Deploy using KIND
|
||||
|
||||
You should first ensure that you have `kind` and `kubectl` installed.
|
||||
If not, you can follow the instructions in the [KIND Quick Start](https://kind.sigs.k8s.io/docs/user/quick-start/).
|
||||
|
||||
```console
|
||||
./hack/kind/kind.sh
|
||||
```
|
||||
|
||||
This will install:
|
||||
- `apex-dev` kind cluster
|
||||
- `ingress-nginx` ingress controller
|
||||
- a rewrite rule in coredns to allow `auth.apex.local` to resolve inside the k8s cluster
|
||||
- the `apex` stack
|
||||
|
||||
## The Apex Agent
|
||||
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
Authentication
|
||||
==============
|
||||
|
||||
The Apex Stack uses OpenID Connect for authentication.
|
||||
It allows whomever deploys the stack to chose any OpenID connect provider they wish in order to provide user authentication.
|
||||
It also enables Apex to focus on its core, and to defer authentication to another service.
|
||||
|
||||
## Web Frontend Authentication
|
||||
|
||||
The web frontend authentication follows the Backend For Frontend (BFF) architecure. The apex stack has 2 components:
|
||||
|
||||
- go-oidc-agent
|
||||
- apiserver
|
||||
|
||||
The [go-oidc-agent](https://github.com/redhat-et/go-oidc-agent) service is a dedicated backend for the web frontend that provides authentication services, and proxies API requests to the apiserver.
|
||||
This not only helps simplify deployment, but also reduces risk of compromise of access tokens/refresh token compromise by keeping them out of the browser.
|
||||
|
||||
For more information on this flow see:
|
||||
- https://github.com/redhat-et/go-oidc-agent
|
||||
- https://auth0.com/blog/backend-for-frontend-pattern-with-auth0-and-dotnet/
|
||||
- https://curity.io/resources/learn/the-token-handler-pattern/
|
||||
|
||||
The apiserver expects to see JWTs in the `Authorization` header, and will validate the JWT signature against the OpenID Providers JWKs.
|
||||
|
||||
## CLI Frontend Authentication
|
||||
|
||||
The cli frontend authentication follows the Backend For Frontend (BFF) architecure also. The apex stack has 2 components:
|
||||
|
||||
- go-oidc-agent
|
||||
- apiserver
|
||||
|
||||
When `go-oidc-agent` is used in Device Flow mode, it simply sends:
|
||||
1. The Device Authorization Endpoint of the Authenication Server
|
||||
1. The Client ID to use
|
||||
|
||||
The Apex CLI is then responsible for acquiring and storing tokens.
|
||||
In this case, the `go-oidc-agent` is there to simplfy deployment only - so no endpoints or client-ids need to be included in the client binary, or injected through config file or flags.
|
||||
@@ -3,25 +3,25 @@ module github.com/redhat-et/apex
|
||||
go 1.19
|
||||
|
||||
require (
|
||||
github.com/MicahParks/keyfunc v1.5.1
|
||||
github.com/bufbuild/connect-go v1.0.0
|
||||
github.com/gin-contrib/cors v1.4.0
|
||||
github.com/coreos/go-oidc v2.2.1+incompatible
|
||||
github.com/gin-gonic/gin v1.8.1
|
||||
github.com/golang-jwt/jwt/v4 v4.4.2
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/metal-stack/go-ipam v1.11.2
|
||||
github.com/ory/dockertest/v3 v3.9.1
|
||||
github.com/pion/stun v0.3.5
|
||||
github.com/redhat-et/go-oidc-agent v0.0.2-0.20221130160039-b98e1621e734
|
||||
github.com/sirupsen/logrus v1.9.0
|
||||
github.com/stretchr/testify v1.8.1
|
||||
github.com/swaggo/files v0.0.0-20220728132757-551d4a08d97a
|
||||
github.com/swaggo/gin-swagger v1.5.3
|
||||
github.com/swaggo/swag v1.8.7
|
||||
github.com/urfave/cli/v2 v2.19.2
|
||||
github.com/urfave/cli/v2 v2.23.5
|
||||
github.com/vishvananda/netlink v1.1.0
|
||||
go.uber.org/zap v1.23.0
|
||||
golang.org/x/net v0.0.0-20220909164309-bea034e7d591
|
||||
golang.org/x/sys v0.1.0
|
||||
golang.org/x/net v0.2.0
|
||||
golang.org/x/oauth2 v0.2.0
|
||||
golang.org/x/sys v0.2.0
|
||||
gopkg.in/ini.v1 v1.67.0
|
||||
gorm.io/driver/postgres v1.4.4
|
||||
gorm.io/driver/sqlite v1.4.3
|
||||
@@ -37,22 +37,29 @@ require (
|
||||
github.com/docker/docker v20.10.18+incompatible // indirect
|
||||
github.com/docker/go-connections v0.4.0 // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/gin-contrib/cors v1.4.0 // indirect
|
||||
github.com/gin-contrib/sessions v0.0.5 // indirect
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
|
||||
github.com/gorilla/context v1.1.1 // indirect
|
||||
github.com/gorilla/securecookie v1.1.1 // indirect
|
||||
github.com/gorilla/sessions v1.2.1 // indirect
|
||||
github.com/imdario/mergo v0.3.12 // indirect
|
||||
github.com/mitchellh/mapstructure v1.4.1 // indirect
|
||||
github.com/moby/term v0.0.0-20220808134915-39b0c02b01ae // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 // indirect
|
||||
github.com/opencontainers/runc v1.1.4 // indirect
|
||||
github.com/pquerna/cachecontrol v0.1.0 // indirect
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
||||
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
gopkg.in/square/go-jose.v2 v2.6.0 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/KyleBanks/depth v1.2.1 // indirect
|
||||
github.com/avast/retry-go/v4 v4.1.0 // indirect
|
||||
github.com/cenkalti/backoff v2.2.1+incompatible
|
||||
github.com/cenkalti/backoff/v4 v4.1.3
|
||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
||||
github.com/coreos/go-semver v0.3.0 // indirect
|
||||
@@ -67,9 +74,9 @@ require (
|
||||
github.com/go-openapi/swag v0.22.3 // indirect
|
||||
github.com/go-playground/locales v0.14.0 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.0 // indirect
|
||||
github.com/go-playground/validator/v10 v10.10.0 // indirect
|
||||
github.com/go-playground/validator/v10 v10.11.1 // indirect
|
||||
github.com/go-redis/redis/v8 v8.11.5 // indirect
|
||||
github.com/goccy/go-json v0.9.7 // indirect
|
||||
github.com/goccy/go-json v0.10.0 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
@@ -88,14 +95,14 @@ require (
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/compress v1.15.9 // indirect
|
||||
github.com/leodido/go-urn v1.2.1 // indirect
|
||||
github.com/lib/pq v1.10.7 // indirect
|
||||
github.com/lib/pq v1.10.7
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/mattn/go-isatty v0.0.14 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.15 // indirect
|
||||
github.com/mattn/go-isatty v0.0.16 // indirect
|
||||
github.com/mattn/go-sqlite3 v2.0.3+incompatible // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/montanaflynn/stats v0.6.6 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.5 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.6 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
@@ -113,9 +120,9 @@ require (
|
||||
go.uber.org/atomic v1.10.0 // indirect
|
||||
go.uber.org/multierr v1.8.0 // indirect
|
||||
go4.org/netipx v0.0.0-20220812043211-3cc044ffd68d // indirect
|
||||
golang.org/x/crypto v0.0.0-20221012134737-56aed061732a // indirect
|
||||
golang.org/x/crypto v0.3.0 // indirect
|
||||
golang.org/x/sync v0.0.0-20220907140024-f12130a52804 // indirect
|
||||
golang.org/x/text v0.3.8 // indirect
|
||||
golang.org/x/text v0.4.0 // indirect
|
||||
golang.org/x/tools v0.1.12 // indirect
|
||||
google.golang.org/genproto v0.0.0-20220909194730-69f6226f97e5 // indirect
|
||||
google.golang.org/grpc v1.49.0 // indirect
|
||||
|
||||
@@ -7,8 +7,6 @@ github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc
|
||||
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
|
||||
github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc=
|
||||
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
|
||||
github.com/MicahParks/keyfunc v1.5.1 h1:RlyyYgKQI/adkIw1yXYtPvTAOb7hBhSX42aH23d8N0Q=
|
||||
github.com/MicahParks/keyfunc v1.5.1/go.mod h1:IdnCilugA0O/99dW+/MkvlyrsX8+L8+x95xuVNtM5jw=
|
||||
github.com/Microsoft/go-winio v0.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA=
|
||||
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
|
||||
github.com/Microsoft/hcsshim v0.9.4 h1:mnUj0ivWy6UzbB1uLFqKR6F+ZyiDc7j4iGgHTpO+5+I=
|
||||
@@ -31,8 +29,6 @@ github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+Ce
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/bufbuild/connect-go v1.0.0 h1:htSflKUT8y1jxhoPhPYTZMrsY3ipUXjjrbcZR5O2cVo=
|
||||
github.com/bufbuild/connect-go v1.0.0/go.mod h1:9iNvh/NOsfhNBUH5CtvXeVUskQO1xsrEviH7ZArwZ3I=
|
||||
github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4=
|
||||
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
|
||||
github.com/cenkalti/backoff/v4 v4.1.3 h1:cFAlzYUlVYDysBEH2T5hyJZMh3+5+WCBvSnK6Q8UtC4=
|
||||
github.com/cenkalti/backoff/v4 v4.1.3/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
@@ -51,6 +47,8 @@ github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkX
|
||||
github.com/containerd/containerd v1.6.8 h1:h4dOFDwzHmqFEP754PgfgTeVXFnLiRc6kiqC7tplDJs=
|
||||
github.com/containerd/continuity v0.3.0 h1:nisirsYROK15TAMVukJOUyGJjz4BNQJBVsNvAXZJ/eg=
|
||||
github.com/containerd/continuity v0.3.0/go.mod h1:wJEAIwKOm/pBZuBd0JmeTvnLquTB1Ag8espWhkykbPM=
|
||||
github.com/coreos/go-oidc v2.2.1+incompatible h1:mh48q/BqXqgjVHpy2ZY7WnWAbenxRjsz9N1i1YxjHAk=
|
||||
github.com/coreos/go-oidc v2.2.1+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=
|
||||
github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM=
|
||||
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
@@ -93,6 +91,8 @@ github.com/gin-contrib/cors v1.4.0 h1:oJ6gwtUl3lqV0WEIwM/LxPF1QZ5qe2lGWdY2+bz7y0
|
||||
github.com/gin-contrib/cors v1.4.0/go.mod h1:bs9pNM0x/UsmHPBWT2xZz9ROh8xYjYkiURUfmBoMlcs=
|
||||
github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4=
|
||||
github.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk=
|
||||
github.com/gin-contrib/sessions v0.0.5 h1:CATtfHmLMQrMNpJRgzjWXD7worTh7g7ritsQfmF+0jE=
|
||||
github.com/gin-contrib/sessions v0.0.5/go.mod h1:vYAuaUPqie3WUSsft6HUlCjlwwoJQs97miaG2+7neKY=
|
||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
github.com/gin-gonic/gin v1.8.1 h1:4+fr/el88TOO3ewCmQr8cx/CtZ/umlIRIs5M4NTNjf8=
|
||||
@@ -122,15 +122,17 @@ github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb
|
||||
github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
|
||||
github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho=
|
||||
github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
|
||||
github.com/go-playground/validator/v10 v10.10.0 h1:I7mrTYv78z8k8VXa/qJlOlEXn/nBh+BF8dHX5nt/dr0=
|
||||
github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
|
||||
github.com/go-playground/validator/v10 v10.11.1 h1:prmOlTVv+YjZjmRmNSF3VmspqJIxJWXmqUsHwfTRRkQ=
|
||||
github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU=
|
||||
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
|
||||
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
|
||||
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
|
||||
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/goccy/go-json v0.9.7 h1:IcB+Aqpx/iMHu5Yooh7jEzJk1JZ7Pjtmys2ukPr7EeM=
|
||||
github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/goccy/go-json v0.10.0 h1:mXKd9Qw4NuzShiRlOXKews24ufknHO7gx30lsDyokKA=
|
||||
github.com/goccy/go-json v0.10.0/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
|
||||
@@ -138,8 +140,6 @@ github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRx
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang-jwt/jwt/v4 v4.4.2 h1:rcc4lwaZgFMCZ5jxF9ABolDcIHdBytAFgqFPbSJQAYs=
|
||||
github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
@@ -178,6 +178,12 @@ github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
|
||||
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
|
||||
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
|
||||
github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI=
|
||||
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
||||
github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
|
||||
@@ -284,11 +290,13 @@ github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope
|
||||
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||
github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI=
|
||||
github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||
github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U=
|
||||
github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/metal-stack/go-ipam v1.11.2 h1:j6DH8puaTzSGOACzEqV8v0lrHkuNfPKeTol8MiexmG8=
|
||||
github.com/metal-stack/go-ipam v1.11.2/go.mod h1:rIaCP9hkHKAZFuICswmdl0SYUv+X2RoHJYLJFQnB01Q=
|
||||
@@ -333,8 +341,8 @@ github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6
|
||||
github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo=
|
||||
github.com/otiai10/mint v1.3.3/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc=
|
||||
github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo=
|
||||
github.com/pelletier/go-toml/v2 v2.0.5 h1:ipoSadvV8oGUjnUbMub59IDPPwfxF694nG/jwbMiyQg=
|
||||
github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas=
|
||||
github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU=
|
||||
github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek=
|
||||
github.com/pierrre/gotestcover v0.0.0-20160517101806-924dca7d15f0/go.mod h1:4xpMLz7RBWyB+ElzHu8Llua96TRCB3YwX+l5EP1wmHk=
|
||||
github.com/pion/stun v0.3.5 h1:uLUCBCkQby4S1cf6CGuR9QrVOKcvUwFeemaC865QHDg=
|
||||
github.com/pion/stun v0.3.5/go.mod h1:gDMim+47EeEtfWogA37n6qXZS88L5V6LqFcf+DZA2UA=
|
||||
@@ -345,6 +353,8 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pquerna/cachecontrol v0.1.0 h1:yJMy84ti9h/+OEWa752kBTKv4XC30OtVVHYv/8cTqKc=
|
||||
github.com/pquerna/cachecontrol v0.1.0/go.mod h1:NrUG3Z7Rdu85UNR3vm7SOsl1nFIeSiQnrHV5K9mBcUI=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
|
||||
@@ -360,6 +370,8 @@ github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R
|
||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
||||
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
||||
github.com/redhat-et/go-oidc-agent v0.0.2-0.20221130160039-b98e1621e734 h1:LbMeu6H1fLMrp4FQ1KbKSqTdZwGiNgDqnkncyJfGzz0=
|
||||
github.com/redhat-et/go-oidc-agent v0.0.2-0.20221130160039-b98e1621e734/go.mod h1:BU0GliOTINM42cNrdkSk1e6PG+Lth5b30SIxyJDvCMM=
|
||||
github.com/robertkrimen/godocdown v0.0.0-20130622164427-0bfa04905481/go.mod h1:C9WhFzY47SzYBIvzFqSvHIR6ROgDo4TtdTuRaOMjF/s=
|
||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
@@ -420,8 +432,8 @@ github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0
|
||||
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
|
||||
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
|
||||
github.com/urfave/cli/v2 v2.19.2 h1:eXu5089gqqiDQKSnFW+H/FhjrxRGztwSxlTsVK7IuqQ=
|
||||
github.com/urfave/cli/v2 v2.19.2/go.mod h1:1CNUng3PtjQMtRzJO4FMXBQvkGtuYRxxiR9xMa7jMwI=
|
||||
github.com/urfave/cli/v2 v2.23.5 h1:xbrU7tAYviSpqeR3X4nEFWUdB/uDZ6DE+HxmRU7Xtyw=
|
||||
github.com/urfave/cli/v2 v2.23.5/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc=
|
||||
github.com/vishvananda/netlink v1.1.0 h1:1iyaYNBLmP6L0220aDnYQpo1QEV4t4hJ+xEEhhJH8j0=
|
||||
github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
|
||||
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df h1:OviZH7qLw/7ZovXvuNyL3XQl8UFofeikI1NW1Gypu7k=
|
||||
@@ -491,10 +503,11 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
|
||||
golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20221012134737-56aed061732a h1:NmSIgad6KjE6VvHciPZuNRTKxGhlPfD6OA87W/PLkqg=
|
||||
golang.org/x/crypto v0.0.0-20221012134737-56aed061732a/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.3.0 h1:a06MkbcxBrEFc0w0QIZWXrH/9cCX6KJyWbBOIwAn+7A=
|
||||
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
@@ -514,6 +527,7 @@ golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73r
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
@@ -528,11 +542,13 @@ golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1
|
||||
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220909164309-bea034e7d591 h1:D0B/7al0LLrVC8aWF4+oxpv/m8bc7ViFfVS8/gXGdqI=
|
||||
golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||
golang.org/x/net v0.2.0 h1:sZfSu1wtKLGlWI4ZZayP0ck9Y73K1ynO6gqzTdBVdPU=
|
||||
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.2.0 h1:GtQkldQ9m7yvzCL1V+LrYow3Khe0eJH0w7RbX/VbaIU=
|
||||
golang.org/x/oauth2 v0.2.0/go.mod h1:Cwn6afJ8jrQwYMxQDTpISoXmXW9I6qF6vDeuuoX3Ibs=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -581,8 +597,9 @@ golang.org/x/sys v0.0.0-20211107104306-e0b2ad06fe42/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A=
|
||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
@@ -593,8 +610,8 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY=
|
||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||
golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
@@ -624,6 +641,8 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
|
||||
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
@@ -665,6 +684,8 @@ gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
|
||||
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI=
|
||||
gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Executable
+20
@@ -0,0 +1,20 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
source $(pwd)/.env
|
||||
|
||||
USERNAME="$1"
|
||||
PASSWORD="$2"
|
||||
|
||||
token=$(curl -s -f -X POST \
|
||||
$APEX_OIDC_URL/token \
|
||||
-H 'Content-Type: application/x-www-form-urlencoded' \
|
||||
-d "username=$USERNAME" \
|
||||
-d "password=$PASSWORD" \
|
||||
-d "scope=openid profile email" \
|
||||
-d "grant_type=password" \
|
||||
-d "client_id=$APEX_OIDC_CLIENT_ID_CLI"
|
||||
)
|
||||
|
||||
echo $token
|
||||
@@ -1,20 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
HOST="localhost:8080/auth"
|
||||
REALM="controller"
|
||||
USERNAME="$1"
|
||||
PASSWORD="$2"
|
||||
CLIENTID='api-clients'
|
||||
CLIENTSECRET='cvXhCRXI2Vld244jjDcnABCMrTEq2rwE'
|
||||
|
||||
token=$(curl -sf -X POST \
|
||||
http://$HOST/realms/$REALM/protocol/openid-connect/token \
|
||||
-H 'Content-Type: application/x-www-form-urlencoded' \
|
||||
-d "username=$USERNAME" \
|
||||
-d "password=$PASSWORD" \
|
||||
-d "grant_type=password" \
|
||||
-d "client_id=$CLIENTID" \
|
||||
-d "client_secret=$CLIENTSECRET" | jq -r ".access_token")
|
||||
|
||||
echo $token
|
||||
@@ -0,0 +1,51 @@
|
||||
Deploy on Kind
|
||||
==============
|
||||
|
||||
|
||||
## Create Cluster
|
||||
|
||||
```console
|
||||
kind create cluster ./deploy/kind.yaml
|
||||
kubectl cluster-info --context kind-apex-dev
|
||||
```
|
||||
|
||||
## Install Ingress Controller
|
||||
|
||||
```console
|
||||
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/kind/deploy.yaml
|
||||
|
||||
kubectl rollout status deployment ingress-nginx-controller -n ingress-nginx --timeout=90s
|
||||
```
|
||||
|
||||
## Fix internal name resolution
|
||||
|
||||
```console
|
||||
kubectl edit configmap -n kube-system coredns
|
||||
```
|
||||
|
||||
Add the following line in `:53{}`
|
||||
```
|
||||
rewrite name auth.apex.local dex.apex.svc.cluster.local
|
||||
```
|
||||
|
||||
Then restart core-dns:
|
||||
```console
|
||||
kubectl rollout restart -n kube-system deployment/coredns
|
||||
kubectl rollout status -n kube-system deployment coredns
|
||||
```
|
||||
|
||||
## Load Images
|
||||
|
||||
```console
|
||||
make images
|
||||
kind load --name apex-dev docker-image quay.io/apex/apiserver:latest
|
||||
kind load --name apex-dev docker-image quay.io/apex/frontend:latest
|
||||
```
|
||||
|
||||
## Install Apex
|
||||
|
||||
```console
|
||||
kubectl create namespace apex
|
||||
kubectl apply -k ./deploy/overlays/dev
|
||||
kubectl rollout status -n apex statefulset ipam
|
||||
```
|
||||
Executable
+58
@@ -0,0 +1,58 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
up() {
|
||||
kind create cluster --config ./deploy/kind.yaml
|
||||
kubectl cluster-info --context kind-apex-dev
|
||||
|
||||
# Deploy Ingress Controller
|
||||
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/kind/deploy.yaml
|
||||
kubectl rollout status deployment ingress-nginx-controller -n ingress-nginx --timeout=90s
|
||||
|
||||
# Add rewrite to CoreDNS
|
||||
kubectl get -n kube-system cm/coredns -o yaml > coredns.yaml
|
||||
sed -i '22i \
|
||||
rewrite name auth.apex.local dex.apex.svc.cluster.local' coredns.yaml
|
||||
kubectl replace -n kube-system -f coredns.yaml
|
||||
rm coredns.yaml
|
||||
kubectl rollout restart -n kube-system deployment/coredns
|
||||
kubectl rollout status -n kube-system deployment coredns --timeout=90s
|
||||
|
||||
# Build images and copy to kind
|
||||
make images
|
||||
kind load --name apex-dev docker-image quay.io/apex/apiserver:latest
|
||||
kind load --name apex-dev docker-image quay.io/apex/frontend:latest
|
||||
|
||||
# Create namespace and deploy apex
|
||||
kubectl create namespace apex
|
||||
kubectl apply -k ./deploy/overlays/dev
|
||||
|
||||
kubectl rollout status -n apex deployment dex --timeout=90s
|
||||
kubectl rollout status -n apex statefulset apiserver --timeout=90s
|
||||
kubectl rollout status -n apex statefulset ipam --timeout=90s
|
||||
kubectl rollout status -n apex deployment backend-web --timeout=90s
|
||||
kubectl rollout status -n apex deployment backend-cli --timeout=90s
|
||||
kubectl rollout status -n apex deployment apiproxy --timeout=90s
|
||||
|
||||
kubectl wait --for=condition=Ready pods --all -n apex --timeout=120s
|
||||
# give k8s a little longer to come up
|
||||
sleep 5
|
||||
}
|
||||
|
||||
down() {
|
||||
kind delete cluster --name apex-dev
|
||||
}
|
||||
|
||||
case $1 in
|
||||
"up")
|
||||
up
|
||||
;;
|
||||
"down")
|
||||
down
|
||||
;;
|
||||
*)
|
||||
echo "command required. up or down"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
@@ -101,4 +101,4 @@ while getopts "u:dh" opt; do
|
||||
done
|
||||
if [ $# -eq 0 ]; then
|
||||
help;exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
Executable
+11
@@ -0,0 +1,11 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
teardown() {
|
||||
./hack/kind/kind.sh down
|
||||
}
|
||||
trap teardown EXIT
|
||||
|
||||
./hack/kind/kind.sh up
|
||||
go test -v --tags=integration ./integration-tests/...
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/cenkalti/backoff/v4"
|
||||
"github.com/ory/dockertest/v3"
|
||||
"github.com/redhat-et/apex/internal/apex"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -26,12 +25,6 @@ func (suite *ApexIntegrationSuite) SetupSuite() {
|
||||
var err error
|
||||
suite.pool, err = dockertest.NewPool("")
|
||||
require.NoError(suite.T(), err)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
|
||||
defer cancel()
|
||||
|
||||
err = backoff.Retry(healthcheck, backoff.WithContext(backoff.NewExponentialBackOff(), ctx))
|
||||
require.NoError(suite.T(), err)
|
||||
}
|
||||
|
||||
func TestApexIntegrationSuite(t *testing.T) {
|
||||
@@ -42,7 +35,10 @@ func (suite *ApexIntegrationSuite) TestBasicConnectivity() {
|
||||
assert := suite.Assert()
|
||||
require := suite.Require()
|
||||
|
||||
token, err := GetToken("admin", "floofykittens")
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
|
||||
defer cancel()
|
||||
|
||||
token, err := getToken(ctx, "admin@apex.local", "floofykittens")
|
||||
require.NoError(err)
|
||||
|
||||
// create the nodes
|
||||
@@ -51,15 +47,12 @@ func (suite *ApexIntegrationSuite) TestBasicConnectivity() {
|
||||
node2 := suite.CreateNode("node2", "bridge", []string{})
|
||||
defer node2.Close()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// start apex on the nodes
|
||||
go func() {
|
||||
_, err = containerExec(ctx, node1, []string{
|
||||
"/bin/apex",
|
||||
fmt.Sprintf("--with-token=%s", token),
|
||||
"http://host.docker.internal:8080",
|
||||
"http://apex.local",
|
||||
})
|
||||
}()
|
||||
|
||||
@@ -67,7 +60,7 @@ func (suite *ApexIntegrationSuite) TestBasicConnectivity() {
|
||||
_, err = containerExec(ctx, node2, []string{
|
||||
"/bin/apex",
|
||||
fmt.Sprintf("--with-token=%s", token),
|
||||
"http://host.docker.internal:8080",
|
||||
"http://apex.local",
|
||||
})
|
||||
}()
|
||||
|
||||
@@ -78,11 +71,11 @@ func (suite *ApexIntegrationSuite) TestBasicConnectivity() {
|
||||
|
||||
suite.T().Logf("Pinging %s from node1", node2IP)
|
||||
err = ping(ctx, node1, node2IP)
|
||||
assert.NoError(err)
|
||||
require.NoError(err)
|
||||
|
||||
suite.T().Logf("Pinging %s from node2", node1IP)
|
||||
err = ping(ctx, node2, node1IP)
|
||||
assert.NoError(err)
|
||||
require.NoError(err)
|
||||
|
||||
//kill the apex process on both nodes
|
||||
_, err = containerExec(ctx, node1, []string{"killall", "apex"})
|
||||
@@ -102,7 +95,7 @@ func (suite *ApexIntegrationSuite) TestBasicConnectivity() {
|
||||
_, err = containerExec(ctx, node1, []string{
|
||||
"/bin/apex",
|
||||
fmt.Sprintf("--with-token=%s", token),
|
||||
"http://host.docker.internal:8080",
|
||||
"http://apex.local",
|
||||
})
|
||||
}()
|
||||
|
||||
@@ -110,7 +103,7 @@ func (suite *ApexIntegrationSuite) TestBasicConnectivity() {
|
||||
_, err = containerExec(ctx, node2, []string{
|
||||
"/bin/apex",
|
||||
fmt.Sprintf("--with-token=%s", token),
|
||||
"http://host.docker.internal:8080",
|
||||
"http://apex.local",
|
||||
})
|
||||
}()
|
||||
|
||||
@@ -138,7 +131,9 @@ func (suite *ApexIntegrationSuite) TestRequestIPDefaultZone() {
|
||||
|
||||
node1IP := "10.200.0.101"
|
||||
node2IP := "10.200.0.102"
|
||||
token, err := GetToken("admin", "floofykittens")
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
|
||||
defer cancel()
|
||||
token, err := getToken(ctx, "admin@apex.local", "floofykittens")
|
||||
require.NoError(err)
|
||||
|
||||
// create the nodes
|
||||
@@ -147,16 +142,13 @@ func (suite *ApexIntegrationSuite) TestRequestIPDefaultZone() {
|
||||
node2 := suite.CreateNode("node2", "bridge", []string{})
|
||||
defer node2.Close()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// start apex on the nodes
|
||||
go func() {
|
||||
_, err = containerExec(ctx, node1, []string{
|
||||
"/bin/apex",
|
||||
fmt.Sprintf("--request-ip=%s", node1IP),
|
||||
fmt.Sprintf("--with-token=%s", token),
|
||||
"http://host.docker.internal:8080",
|
||||
"http://apex.local",
|
||||
})
|
||||
}()
|
||||
|
||||
@@ -165,7 +157,7 @@ func (suite *ApexIntegrationSuite) TestRequestIPDefaultZone() {
|
||||
"/bin/apex",
|
||||
fmt.Sprintf("--request-ip=%s", node2IP),
|
||||
fmt.Sprintf("--with-token=%s", token),
|
||||
"http://host.docker.internal:8080",
|
||||
"http://apex.local",
|
||||
})
|
||||
}()
|
||||
|
||||
@@ -183,18 +175,20 @@ func (suite *ApexIntegrationSuite) TestRequestIPDefaultZone() {
|
||||
func (suite *ApexIntegrationSuite) TestRequestIPZone() {
|
||||
assert := suite.Assert()
|
||||
require := suite.Require()
|
||||
token, err := GetToken("kitteh1", "floofykittens")
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
|
||||
defer cancel()
|
||||
token, err := getToken(ctx, "kitteh1@apex.local", "floofykittens")
|
||||
require.NoError(err)
|
||||
|
||||
c, err := newClient(token)
|
||||
c, err := newClient(ctx, token)
|
||||
require.NoError(err)
|
||||
// create a new zone
|
||||
zoneID, err := c.CreateZone("zone-blue", "zone full of blue things", "10.140.0.0/24", false)
|
||||
assert.NoError(err)
|
||||
require.NoError(err)
|
||||
|
||||
// patch the new user into the zone
|
||||
_, err = c.MoveCurrentUserToZone(zoneID.ID)
|
||||
assert.NoError(err)
|
||||
require.NoError(err)
|
||||
|
||||
node1IP := "10.140.0.101"
|
||||
node2IP := "10.140.0.102"
|
||||
@@ -205,16 +199,13 @@ func (suite *ApexIntegrationSuite) TestRequestIPZone() {
|
||||
node2 := suite.CreateNode("node2", "bridge", []string{})
|
||||
defer node2.Close()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// start apex on the nodes
|
||||
go func() {
|
||||
_, err = containerExec(ctx, node1, []string{
|
||||
"/bin/apex",
|
||||
fmt.Sprintf("--request-ip=%s", node1IP),
|
||||
fmt.Sprintf("--with-token=%s", token),
|
||||
"http://host.docker.internal:8080",
|
||||
"http://apex.local",
|
||||
})
|
||||
}()
|
||||
|
||||
@@ -223,7 +214,7 @@ func (suite *ApexIntegrationSuite) TestRequestIPZone() {
|
||||
"/bin/apex",
|
||||
fmt.Sprintf("--request-ip=%s", node2IP),
|
||||
fmt.Sprintf("--with-token=%s", token),
|
||||
"http://host.docker.internal:8080",
|
||||
"http://apex.local",
|
||||
})
|
||||
}()
|
||||
|
||||
@@ -249,7 +240,7 @@ func (suite *ApexIntegrationSuite) TestRequestIPZone() {
|
||||
"/bin/apex",
|
||||
fmt.Sprintf("--request-ip=%s", node1IP),
|
||||
fmt.Sprintf("--with-token=%s", token),
|
||||
"http://host.docker.internal:8080",
|
||||
"http://apex.local",
|
||||
})
|
||||
}()
|
||||
|
||||
@@ -258,7 +249,7 @@ func (suite *ApexIntegrationSuite) TestRequestIPZone() {
|
||||
"/bin/apex",
|
||||
fmt.Sprintf("--request-ip=%s", node2IP),
|
||||
fmt.Sprintf("--with-token=%s", token),
|
||||
"http://host.docker.internal:8080",
|
||||
"http://apex.local",
|
||||
})
|
||||
}()
|
||||
|
||||
@@ -276,19 +267,21 @@ func (suite *ApexIntegrationSuite) TestRequestIPZone() {
|
||||
func (suite *ApexIntegrationSuite) TestHubZone() {
|
||||
assert := suite.Assert()
|
||||
require := suite.Require()
|
||||
token, err := GetToken("kitteh2", "floofykittens")
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
|
||||
defer cancel()
|
||||
token, err := getToken(ctx, "kitteh2@apex.local", "floofykittens")
|
||||
require.NoError(err)
|
||||
|
||||
c, err := newClient(token)
|
||||
c, err := newClient(ctx, token)
|
||||
require.NoError(err)
|
||||
|
||||
// create a new zone
|
||||
zoneID, err := c.CreateZone("zone-relay", "zone with a relay hub", "10.162.0.0/24", true)
|
||||
assert.NoError(err)
|
||||
require.NoError(err)
|
||||
|
||||
// patch the new user into the zone
|
||||
_, err = c.MoveCurrentUserToZone(zoneID.ID)
|
||||
assert.NoError(err)
|
||||
require.NoError(err)
|
||||
|
||||
// create the nodes
|
||||
node1 := suite.CreateNode("node1", "bridge", []string{})
|
||||
@@ -296,10 +289,7 @@ func (suite *ApexIntegrationSuite) TestHubZone() {
|
||||
node2 := suite.CreateNode("node2", "bridge", []string{})
|
||||
defer node2.Close()
|
||||
node3 := suite.CreateNode("node3", "bridge", []string{})
|
||||
defer node2.Close()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
|
||||
defer cancel()
|
||||
defer node3.Close()
|
||||
|
||||
// start apex on the nodes
|
||||
go func() {
|
||||
@@ -307,7 +297,7 @@ func (suite *ApexIntegrationSuite) TestHubZone() {
|
||||
"/bin/apex",
|
||||
"--hub-router",
|
||||
fmt.Sprintf("--with-token=%s", token),
|
||||
"http://host.docker.internal:8080",
|
||||
"http://apex.local",
|
||||
})
|
||||
}()
|
||||
|
||||
@@ -317,7 +307,7 @@ func (suite *ApexIntegrationSuite) TestHubZone() {
|
||||
_, err = containerExec(ctx, node2, []string{
|
||||
"/bin/apex",
|
||||
fmt.Sprintf("--with-token=%s", token),
|
||||
"http://host.docker.internal:8080",
|
||||
"http://apex.local",
|
||||
})
|
||||
}()
|
||||
|
||||
@@ -325,7 +315,7 @@ func (suite *ApexIntegrationSuite) TestHubZone() {
|
||||
_, err = containerExec(ctx, node3, []string{
|
||||
"/bin/apex",
|
||||
fmt.Sprintf("--with-token=%s", token),
|
||||
"http://host.docker.internal:8080",
|
||||
"http://apex.local",
|
||||
})
|
||||
}()
|
||||
|
||||
@@ -357,19 +347,21 @@ func (suite *ApexIntegrationSuite) TestHubZone() {
|
||||
func (suite *ApexIntegrationSuite) TestChildPrefix() {
|
||||
assert := suite.Assert()
|
||||
require := suite.Require()
|
||||
token, err := GetToken("kitteh3", "floofykittens")
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
|
||||
defer cancel()
|
||||
token, err := getToken(ctx, "kitteh3@apex.local", "floofykittens")
|
||||
require.NoError(err)
|
||||
|
||||
c, err := newClient(token)
|
||||
c, err := newClient(ctx, token)
|
||||
require.NoError(err)
|
||||
|
||||
// create a new zone
|
||||
zoneID, err := c.CreateZone("zone-child-prefix", "zone full of toddler prefixes", "100.64.100.0/24", false)
|
||||
assert.NoError(err)
|
||||
require.NoError(err)
|
||||
|
||||
// patch the new user into the zone
|
||||
_, err = c.MoveCurrentUserToZone(zoneID.ID)
|
||||
assert.NoError(err)
|
||||
require.NoError(err)
|
||||
|
||||
node1LoopbackNet := "172.16.10.101/32"
|
||||
node2LoopbackNet := "172.16.20.102/32"
|
||||
@@ -382,16 +374,13 @@ func (suite *ApexIntegrationSuite) TestChildPrefix() {
|
||||
node2 := suite.CreateNode("node2", "bridge", []string{})
|
||||
defer node2.Close()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 40*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// start apex on the nodes
|
||||
go func() {
|
||||
_, err = containerExec(ctx, node1, []string{
|
||||
"/bin/apex",
|
||||
fmt.Sprintf("--child-prefix=%s", node1ChildPrefix),
|
||||
fmt.Sprintf("--with-token=%s", token),
|
||||
"http://host.docker.internal:8080",
|
||||
"http://apex.local",
|
||||
})
|
||||
}()
|
||||
|
||||
@@ -400,15 +389,15 @@ func (suite *ApexIntegrationSuite) TestChildPrefix() {
|
||||
"/bin/apex",
|
||||
fmt.Sprintf("--child-prefix=%s", node2ChildPrefix),
|
||||
fmt.Sprintf("--with-token=%s", token),
|
||||
"http://host.docker.internal:8080",
|
||||
"http://apex.local",
|
||||
})
|
||||
}()
|
||||
|
||||
// add loopbacks to the containers that are contained in the node's child prefix
|
||||
_, err = containerExec(ctx, node1, []string{"ip", "addr", "add", node1LoopbackNet, "dev", "lo"})
|
||||
assert.NoError(err)
|
||||
require.NoError(err)
|
||||
_, err = containerExec(ctx, node2, []string{"ip", "addr", "add", node2LoopbackNet, "dev", "lo"})
|
||||
assert.NoError(err)
|
||||
require.NoError(err)
|
||||
|
||||
// parse the loopback ip from the loopback prefix
|
||||
node1LoopbackIP, _, _ := net.ParseCIDR(node1LoopbackNet)
|
||||
@@ -465,14 +454,17 @@ func (suite *ApexIntegrationSuite) TestRelayNAT() {
|
||||
net2Spoke1Name := "net2-spoke1"
|
||||
net1Spoke2Name := "net1-spoke2"
|
||||
net2Spoke2Name := "net2-spoke2"
|
||||
controllerURL := "http://172.17.0.1:8080"
|
||||
controllerURL := "http://apex.local"
|
||||
|
||||
// launch a relay node in the default namespace that all spokes can reach
|
||||
relayNode := suite.CreateNode(relayNodeName, defaultNSNet, []string{})
|
||||
defer relayNode.Close()
|
||||
|
||||
_ = suite.CreateNetwork("net1", "100.64.11.0/24")
|
||||
_ = suite.CreateNetwork("net2", "100.64.12.0/24")
|
||||
dNet1 := suite.CreateNetwork(net1, "100.64.11.0/24")
|
||||
defer dNet1.Close()
|
||||
|
||||
dNet2 := suite.CreateNetwork(net2, "100.64.12.0/24")
|
||||
defer dNet2.Close()
|
||||
|
||||
// launch nat nodes
|
||||
natNodeNet1 := suite.CreateNode("net1-nat", net1, []string{})
|
||||
@@ -480,7 +472,7 @@ func (suite *ApexIntegrationSuite) TestRelayNAT() {
|
||||
natNodeNet2 := suite.CreateNode("net2-nat", net2, []string{})
|
||||
defer natNodeNet2.Close()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 90*time.Second)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// attach nat nodes to the spoke networks
|
||||
@@ -503,13 +495,13 @@ func (suite *ApexIntegrationSuite) TestRelayNAT() {
|
||||
|
||||
// create spoke nodes
|
||||
net1SpokeNode1 := suite.CreateNode(net1Spoke1Name, net1, []string{})
|
||||
defer natNodeNet1.Close()
|
||||
defer net1SpokeNode1.Close()
|
||||
net2SpokeNode1 := suite.CreateNode(net2Spoke1Name, net2, []string{})
|
||||
defer natNodeNet2.Close()
|
||||
defer net2SpokeNode1.Close()
|
||||
net1SpokeNode2 := suite.CreateNode(net1Spoke2Name, net1, []string{})
|
||||
defer natNodeNet1.Close()
|
||||
defer net1SpokeNode2.Close()
|
||||
net2SpokeNode2 := suite.CreateNode(net2Spoke2Name, net2, []string{})
|
||||
defer natNodeNet2.Close()
|
||||
defer net2SpokeNode2.Close()
|
||||
|
||||
// delete the default route pointing to the nat gateway
|
||||
_, err = containerExec(ctx, net1SpokeNode1, []string{"ip", "-4", "route", "del", "default"})
|
||||
@@ -535,26 +527,26 @@ func (suite *ApexIntegrationSuite) TestRelayNAT() {
|
||||
suite.T().Logf("Validate NAT Infra: Pinging %s from net2-spoke1", docker0)
|
||||
err = ping(ctx, net2SpokeNode1, docker0)
|
||||
assert.NoError(err)
|
||||
suite.T().Logf("Validate NAT Infra: Pinging %s from net1-spoke1", docker0)
|
||||
suite.T().Logf("Validate NAT Infra: Pinging %s from net1-spoke2", docker0)
|
||||
err = ping(ctx, net1SpokeNode2, docker0)
|
||||
assert.NoError(err)
|
||||
suite.T().Logf("Validate NAT Infra: Pinging %s from net2-spoke1", docker0)
|
||||
suite.T().Logf("Validate NAT Infra: Pinging %s from net2-spoke2", docker0)
|
||||
err = ping(ctx, net2SpokeNode2, docker0)
|
||||
assert.NoError(err)
|
||||
|
||||
token, err := GetToken("kitteh4", "floofykittens")
|
||||
token, err := getToken(ctx, "kitteh4@apex.local", "floofykittens")
|
||||
require.NoError(err)
|
||||
|
||||
c, err := newClient(token)
|
||||
c, err := newClient(ctx, token)
|
||||
require.NoError(err)
|
||||
|
||||
// create a new zone
|
||||
zoneID, err := c.CreateZone("zone-nat-relay", "nat test zone", "10.29.0.0/24", true)
|
||||
assert.NoError(err)
|
||||
require.NoError(err)
|
||||
|
||||
// patch the new user into the zone
|
||||
_, err = c.MoveCurrentUserToZone(zoneID.ID)
|
||||
assert.NoError(err)
|
||||
require.NoError(err)
|
||||
|
||||
// start apex on the nodes
|
||||
go func() {
|
||||
|
||||
@@ -11,43 +11,18 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/cenkalti/backoff/v4"
|
||||
"github.com/coreos/go-oidc"
|
||||
"github.com/ory/dockertest/v3"
|
||||
"github.com/ory/dockertest/v3/docker"
|
||||
"github.com/redhat-et/apex/internal/client"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
const (
|
||||
controller = "http://localhost:8080"
|
||||
clientId = "api-clients"
|
||||
clientSecret = "cvXhCRXI2Vld244jjDcnABCMrTEq2rwE"
|
||||
)
|
||||
|
||||
func healthcheck() error {
|
||||
res, err := http.Get(fmt.Sprintf("%s/api/health", controller))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("got %d, wanted 200", res.StatusCode)
|
||||
}
|
||||
defer res.Body.Close()
|
||||
body, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !strings.Contains(string(body), "ok") {
|
||||
return fmt.Errorf("service is not healthy")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateNode creates a container
|
||||
func (suite *ApexIntegrationSuite) CreateNode(name, network string, args []string) *dockertest.Resource {
|
||||
options := &dockertest.RunOptions{
|
||||
@@ -63,11 +38,13 @@ func (suite *ApexIntegrationSuite) CreateNode(name, network string, args []strin
|
||||
"NET_RAW",
|
||||
},
|
||||
ExtraHosts: []string{
|
||||
"host.docker.internal:host-gateway",
|
||||
"apex.local:host-gateway",
|
||||
"api.apex.local:host-gateway",
|
||||
"auth.apex.local:host-gateway",
|
||||
},
|
||||
}
|
||||
hostConfig := func(config *docker.HostConfig) {
|
||||
//config.AutoRemove = true
|
||||
// config.AutoRemove = true
|
||||
config.RestartPolicy = docker.RestartPolicy{
|
||||
Name: "no",
|
||||
}
|
||||
@@ -79,47 +56,8 @@ func (suite *ApexIntegrationSuite) CreateNode(name, network string, args []strin
|
||||
return node
|
||||
}
|
||||
|
||||
// GetToken creates a new auth token
|
||||
func GetToken(username, password string) (string, error) {
|
||||
v := url.Values{}
|
||||
v.Set("username", username)
|
||||
v.Set("password", password)
|
||||
v.Set("client_id", clientId)
|
||||
v.Set("client_secret", clientSecret)
|
||||
v.Set("grant_type", "password")
|
||||
|
||||
res, err := http.PostForm(fmt.Sprintf("%s/auth/realms/controller/protocol/openid-connect/token", controller), v)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if res.StatusCode != http.StatusOK {
|
||||
return "", err
|
||||
}
|
||||
var r map[string]interface{}
|
||||
if err := json.Unmarshal(body, &r); err != nil {
|
||||
return "", err
|
||||
}
|
||||
token, ok := r["access_token"]
|
||||
if !ok {
|
||||
return "", fmt.Errorf("no access token in reponse")
|
||||
}
|
||||
return token.(string), nil
|
||||
}
|
||||
|
||||
func newClient(token string) (client.Client, error) {
|
||||
auth := client.NewTokenAuthenticator(token)
|
||||
client, err := client.NewClient(controller, auth)
|
||||
if err != nil {
|
||||
return client, err
|
||||
}
|
||||
return client, nil
|
||||
func newClient(ctx context.Context, token string) (*client.Client, error) {
|
||||
return client.NewClient(ctx, "http://api.apex.local", client.WithToken(token))
|
||||
}
|
||||
|
||||
func getContainerIfaceIP(ctx context.Context, dev string, container *dockertest.Resource) (string, error) {
|
||||
@@ -194,6 +132,43 @@ func containerExec(ctx context.Context, container *dockertest.Resource, cmd []st
|
||||
return stdout.String(), err
|
||||
}
|
||||
|
||||
func getToken(ctx context.Context, username, password string) (string, error) {
|
||||
provider, err := oidc.NewProvider(ctx, "http://auth.apex.local")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
config := oauth2.Config{
|
||||
ClientID: "apex-cli",
|
||||
//ClientSecret: "dhEN2dsqyUg5qmaDAdqi4CmH",
|
||||
Endpoint: provider.Endpoint(),
|
||||
Scopes: []string{"openid", "profile", "email"},
|
||||
}
|
||||
|
||||
token, err := config.PasswordCredentialsToken(ctx, username, password)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
data, err := json.Marshal(token)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var rawToken map[string]interface{}
|
||||
if err := json.Unmarshal(data, &rawToken); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
rawToken["id_token"] = token.Extra("id_token")
|
||||
|
||||
data, err = json.Marshal(rawToken)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return string(data), err
|
||||
}
|
||||
|
||||
// CreateNetwork creates a docker network
|
||||
func (suite *ApexIntegrationSuite) CreateNetwork(name, cidr string) *dockertest.Network {
|
||||
net, err := suite.pool.CreateNetwork(name, func(config *docker.CreateNetworkOptions) {
|
||||
@@ -208,7 +183,6 @@ func (suite *ApexIntegrationSuite) CreateNetwork(name, cidr string) *dockertest.
|
||||
}
|
||||
})
|
||||
require.NoError(suite.T(), err)
|
||||
|
||||
return net
|
||||
}
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ type Apex struct {
|
||||
hubRouterWgIP string
|
||||
os string
|
||||
wgConfig wgConfig
|
||||
client client.Client
|
||||
client *client.Client
|
||||
controllerURL *url.URL
|
||||
// caches peers by their UUID
|
||||
peerCache map[uuid.UUID]models.Peer
|
||||
@@ -83,19 +83,19 @@ func NewApex(ctx context.Context, cCtx *cli.Context) (*Apex, error) {
|
||||
log.Fatalf("error: <controller-url> is not a valid URL: %s", err)
|
||||
}
|
||||
|
||||
// Force controller URL be api.${DOMAIN}
|
||||
controllerURL.Host = "api." + controllerURL.Host
|
||||
controllerURL.Path = ""
|
||||
|
||||
withToken := cCtx.String("with-token")
|
||||
var auth client.Authenticator
|
||||
var option client.Option
|
||||
if withToken == "" {
|
||||
var err error
|
||||
auth, err = client.NewDeviceFlowAuthenticator(ctx, controllerURL)
|
||||
if err != nil {
|
||||
log.Fatalf("authentication error: %+v", err)
|
||||
}
|
||||
option = client.WithDeviceFlow()
|
||||
} else {
|
||||
auth = client.NewTokenAuthenticator(withToken)
|
||||
option = client.WithToken(withToken)
|
||||
}
|
||||
|
||||
client, err := client.NewClient(controller, auth)
|
||||
client, err := client.NewClient(ctx, controllerURL.String(), option)
|
||||
if err != nil {
|
||||
log.Fatalf("error creating client: %+v", err)
|
||||
}
|
||||
|
||||
+88
-139
@@ -9,21 +9,11 @@ import (
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
agent "github.com/redhat-et/go-oidc-agent"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
// TODO: These consts witll differ from installation to installation.
|
||||
// Need to find a way to provide these dynamically (config file) etc...
|
||||
const (
|
||||
APEX_CLIENT_ID = "apex-cli"
|
||||
APEX_CLIENT_SECRET = "QkskUDQenfXRxWx9UA0TeuwmOnHilHtQ"
|
||||
LOGIN_URL = "/auth/realms/controller/protocol/openid-connect/auth/device"
|
||||
VERIFICATION_URI = "/auth/realms/controller/device"
|
||||
VERIFY_URL = "/auth/realms/controller/protocol/openid-connect/token"
|
||||
GRANT_TYPE = "urn:ietf:params:oauth:grant-type:device_code"
|
||||
)
|
||||
|
||||
type TokenResponse struct {
|
||||
type deviceFlowResponse struct {
|
||||
DeviceCode string `json:"device_code"`
|
||||
UserCode string `json:"user_code"`
|
||||
VerificationURI string `json:"verification_uri"`
|
||||
@@ -32,125 +22,67 @@ type TokenResponse struct {
|
||||
Interval int `json:"interval"`
|
||||
}
|
||||
|
||||
type Authenticator interface {
|
||||
Token() (string, error)
|
||||
}
|
||||
|
||||
type TokenAuthenticator struct {
|
||||
accessToken string
|
||||
}
|
||||
|
||||
func NewTokenAuthenticator(token string) Authenticator {
|
||||
return &TokenAuthenticator{
|
||||
accessToken: token,
|
||||
}
|
||||
}
|
||||
|
||||
func (a *TokenAuthenticator) Token() (string, error) {
|
||||
return a.accessToken, nil
|
||||
}
|
||||
|
||||
type DeviceFlowAuthenticator struct {
|
||||
hostname *url.URL
|
||||
accessToken string
|
||||
refreshToken string
|
||||
tokenExpiry time.Time
|
||||
refreshExpiry time.Time
|
||||
}
|
||||
|
||||
func NewDeviceFlowAuthenticator(ctx context.Context, hostname *url.URL) (*DeviceFlowAuthenticator, error) {
|
||||
a := &DeviceFlowAuthenticator{
|
||||
hostname: hostname,
|
||||
}
|
||||
func newDeviceFlowToken(ctx context.Context, deviceEndpoint, tokenEndpoint, clientID string) (*oauth2.Token, interface{}, error) {
|
||||
requestTime := time.Now()
|
||||
token, err := getToken(a.hostname)
|
||||
d, err := startDeviceFlow(deviceEndpoint, clientID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
fmt.Println("Your device must be registered with Apex Controller.")
|
||||
fmt.Printf("Your one-time code is: %s\n", token.UserCode)
|
||||
fmt.Println("Please open the following URL in your browser and enter your one-time code:")
|
||||
dest, err := url.JoinPath(a.hostname.String(), VERIFICATION_URI)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fmt.Printf("%s\n", dest)
|
||||
fmt.Println("Your device must be registered with Apex.")
|
||||
fmt.Printf("Your one-time code is: %s\n", d.UserCode)
|
||||
fmt.Println("Please open the following URL in your browser to sign in:")
|
||||
fmt.Printf("%s\n", d.VerificationURIComplete)
|
||||
|
||||
var token *oauth2.Token
|
||||
var idToken interface{}
|
||||
c := make(chan error, 1)
|
||||
ctx, cancel := context.WithTimeout(ctx, time.Duration(token.ExpiresIn)*time.Second)
|
||||
ctx, cancel := context.WithTimeout(ctx, time.Duration(d.ExpiresIn)*time.Second)
|
||||
defer cancel()
|
||||
go func() {
|
||||
c <- a.pollForResponse(ctx, token, requestTime)
|
||||
token, idToken, err = pollForResponse(ctx, clientID, tokenEndpoint, d, requestTime)
|
||||
c <- err
|
||||
}()
|
||||
|
||||
err = <-c
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
fmt.Println("Authentication succeeded.")
|
||||
return a, nil
|
||||
|
||||
return token, idToken, nil
|
||||
}
|
||||
|
||||
func (a *DeviceFlowAuthenticator) Token() (string, error) {
|
||||
if time.Now().After(a.tokenExpiry) {
|
||||
log.Debugf("Access token has expired. Requesting a new one")
|
||||
if time.Now().After(a.refreshExpiry) {
|
||||
return "", fmt.Errorf("refresh token has expired")
|
||||
}
|
||||
if err := a.refreshTokens(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
if a.accessToken != "" {
|
||||
return a.accessToken, nil
|
||||
}
|
||||
return "", fmt.Errorf("not authenticated")
|
||||
}
|
||||
|
||||
func (a *DeviceFlowAuthenticator) refreshTokens() error {
|
||||
v := url.Values{}
|
||||
v.Set("client_id", APEX_CLIENT_ID)
|
||||
v.Set("client_secret", APEX_CLIENT_SECRET)
|
||||
v.Set("grant_type", "refresh_token")
|
||||
v.Set("refresh_token", a.refreshToken)
|
||||
dest, err := url.JoinPath(a.hostname.String(), VERIFY_URL)
|
||||
func startLogin(hostname url.URL) (*agent.DeviceStartReponse, error) {
|
||||
dest := hostname
|
||||
dest.Path = "/login/start"
|
||||
res, err := http.Post(dest.String(), "application/json", nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
requestTime := time.Now()
|
||||
res, err := http.PostForm(dest, v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
body, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
return err
|
||||
return nil, fmt.Errorf("request %s failed with %d", dest.String(), res.StatusCode)
|
||||
}
|
||||
var r tokenReponse
|
||||
if err := json.Unmarshal(body, &r); err != nil {
|
||||
return err
|
||||
}
|
||||
a.accessToken = r.AccessToken
|
||||
a.tokenExpiry = requestTime.Add(time.Duration(r.ExpiresIn) * time.Second)
|
||||
a.refreshToken = r.RefreshToken
|
||||
a.refreshExpiry = requestTime.Add(time.Duration(r.RefreshExpiresIn) * time.Second)
|
||||
return nil
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
func getToken(hostname *url.URL) (*TokenResponse, error) {
|
||||
v := url.Values{}
|
||||
v.Set("client_id", APEX_CLIENT_ID)
|
||||
v.Set("client_secret", APEX_CLIENT_SECRET)
|
||||
dest, err := url.JoinPath(hostname.String(), LOGIN_URL)
|
||||
body, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, err := http.PostForm(dest, v)
|
||||
|
||||
var resp agent.DeviceStartReponse
|
||||
if err = json.Unmarshal(body, &resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
func startDeviceFlow(deviceEndpoint string, clientID string) (*deviceFlowResponse, error) {
|
||||
v := url.Values{}
|
||||
v.Set("client_id", clientID)
|
||||
v.Set("scope", "openid profile email")
|
||||
res, err := http.PostForm(deviceEndpoint, v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -165,7 +97,7 @@ func getToken(hostname *url.URL) (*TokenResponse, error) {
|
||||
return nil, fmt.Errorf("http error: %s", string(body))
|
||||
}
|
||||
|
||||
var t TokenResponse
|
||||
var t deviceFlowResponse
|
||||
if err := json.Unmarshal(body, &t); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -173,57 +105,74 @@ func getToken(hostname *url.URL) (*TokenResponse, error) {
|
||||
return &t, nil
|
||||
}
|
||||
|
||||
type tokenReponse struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
ExpiresIn int `json:"expires_in"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
RefreshExpiresIn int `json:"refresh_expires_in"`
|
||||
}
|
||||
const (
|
||||
errAuthorizationPending = "authorization_pending"
|
||||
errSlowDown = "slow_down"
|
||||
errAccessDenied = "access_denied"
|
||||
errExpiredToken = "expired_token"
|
||||
)
|
||||
|
||||
func (a *DeviceFlowAuthenticator) pollForResponse(ctx context.Context, t *TokenResponse, requestTime time.Time) error {
|
||||
func pollForResponse(ctx context.Context, clientID string, tokenURL string, t *deviceFlowResponse, requestTime time.Time) (*oauth2.Token, interface{}, error) {
|
||||
v := url.Values{}
|
||||
v.Set("device_code", t.DeviceCode)
|
||||
v.Set("client_id", APEX_CLIENT_ID)
|
||||
v.Set("client_secret", APEX_CLIENT_SECRET)
|
||||
v.Set("grant_type", GRANT_TYPE)
|
||||
v.Set("client_id", clientID)
|
||||
v.Set("grant_type", "urn:ietf:params:oauth:grant-type:device_code")
|
||||
|
||||
ticker := time.NewTicker(time.Duration(t.Interval) * time.Second)
|
||||
var r tokenReponse
|
||||
LOOP:
|
||||
interval := t.Interval
|
||||
if interval == 0 {
|
||||
// Pick a reasonable default if none is set
|
||||
interval = 5
|
||||
}
|
||||
ticker := time.NewTicker(time.Duration(interval) * time.Second)
|
||||
var token oauth2.Token
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
return nil, nil, ctx.Err()
|
||||
case <-ticker.C:
|
||||
dest, err := url.JoinPath(a.hostname.String(), VERIFY_URL)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
res, err := http.PostForm(dest, v)
|
||||
res, err := http.PostForm(tokenURL, v)
|
||||
if err != nil {
|
||||
// possible transient connection error, continue retrying
|
||||
continue
|
||||
}
|
||||
defer res.Body.Close()
|
||||
body, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
// possible transient connection error, continue retrying
|
||||
continue
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
continue
|
||||
type errorResponse struct {
|
||||
Error string `json:"error"`
|
||||
}
|
||||
var r errorResponse
|
||||
if err := json.Unmarshal(body, &r); err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
if r.Error != "" {
|
||||
if r.Error == errSlowDown {
|
||||
// adjust interval and continue retrying
|
||||
interval += 5
|
||||
ticker.Reset(time.Duration(interval) * time.Second)
|
||||
continue
|
||||
} else if r.Error == errAccessDenied || r.Error == errExpiredToken {
|
||||
return nil, nil, fmt.Errorf("failed to get token: %s", r.Error)
|
||||
}
|
||||
// error was either authorization_pending or something else
|
||||
// continue to poll for a token
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(body, &r); err != nil {
|
||||
continue
|
||||
if err := json.Unmarshal(body, &token); err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
if r.AccessToken != "" {
|
||||
break LOOP
|
||||
var tokenRaw map[string]interface{}
|
||||
if err = json.Unmarshal(body, &tokenRaw); err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
idToken := tokenRaw["id_token"]
|
||||
return &token, idToken, nil
|
||||
}
|
||||
}
|
||||
a.accessToken = r.AccessToken
|
||||
a.tokenExpiry = requestTime.Add(time.Duration(r.ExpiresIn) * time.Second)
|
||||
a.refreshToken = r.RefreshToken
|
||||
a.refreshExpiry = requestTime.Add(time.Duration(r.RefreshExpiresIn) * time.Second)
|
||||
return nil
|
||||
}
|
||||
|
||||
+76
-17
@@ -1,34 +1,93 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/coreos/go-oidc"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
options *options
|
||||
baseURL *url.URL
|
||||
auth Authenticator
|
||||
client *http.Client
|
||||
}
|
||||
|
||||
func NewClient(addr string, auth Authenticator) (Client, error) {
|
||||
baseURL, err := url.Parse(addr)
|
||||
if err != nil {
|
||||
return Client{}, err
|
||||
}
|
||||
return Client{
|
||||
baseURL: baseURL,
|
||||
auth: auth,
|
||||
client: http.DefaultClient,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *Client) do(req *http.Request) (*http.Response, error) {
|
||||
accessToken, err := c.auth.Token()
|
||||
func NewClient(ctx context.Context, addr string, options ...Option) (*Client, error) {
|
||||
opts, err := newOptions(options...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("authorization", fmt.Sprintf("bearer %s", accessToken))
|
||||
return c.client.Do(req)
|
||||
|
||||
baseURL, err := url.Parse(addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c := Client{
|
||||
options: opts,
|
||||
baseURL: baseURL,
|
||||
}
|
||||
|
||||
resp, err := startLogin(*baseURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
provider, err := oidc.NewProvider(ctx, resp.Issuer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
oidcConfig := &oidc.Config{
|
||||
ClientID: resp.ClientID,
|
||||
}
|
||||
|
||||
verifier := provider.Verifier(oidcConfig)
|
||||
|
||||
config := &oauth2.Config{
|
||||
ClientID: resp.ClientID,
|
||||
ClientSecret: c.options.clientSecret,
|
||||
Endpoint: provider.Endpoint(),
|
||||
Scopes: []string{"openid", "profile", "email"},
|
||||
}
|
||||
|
||||
var token *oauth2.Token
|
||||
var rawIdToken interface{}
|
||||
if c.options.deviceFlow {
|
||||
token, rawIdToken, err = newDeviceFlowToken(ctx, resp.DeviceAuthURL, provider.Endpoint().TokenURL, resp.ClientID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else if c.options.token == "" {
|
||||
token, err = config.PasswordCredentialsToken(ctx, c.options.username, c.options.password)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rawIdToken = token.Extra("id_token")
|
||||
} else {
|
||||
if err = json.Unmarshal([]byte(c.options.token), &token); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var tokenRaw map[string]interface{}
|
||||
if err = json.Unmarshal([]byte(c.options.token), &tokenRaw); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rawIdToken = tokenRaw["id_token"]
|
||||
}
|
||||
if rawIdToken == nil {
|
||||
return nil, fmt.Errorf("no id_token in response")
|
||||
}
|
||||
|
||||
if _, err = verifier.Verify(ctx, rawIdToken.(string)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c.client = config.Client(ctx, token)
|
||||
|
||||
return &c, nil
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ func (c *Client) CreateDevice(publicKey string, hostname string) (models.Device,
|
||||
return models.Device{}, err
|
||||
}
|
||||
|
||||
res, err := c.do(r)
|
||||
res, err := c.client.Do(r)
|
||||
if err != nil {
|
||||
return models.Device{}, err
|
||||
}
|
||||
@@ -61,7 +61,7 @@ func (c *Client) GetDevice(deviceID uuid.UUID) (models.Device, error) {
|
||||
return models.Device{}, err
|
||||
}
|
||||
|
||||
res, err := c.do(r)
|
||||
res, err := c.client.Do(r)
|
||||
if err != nil {
|
||||
return models.Device{}, err
|
||||
}
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
package client
|
||||
|
||||
type options struct {
|
||||
deviceFlow bool
|
||||
clientSecret string
|
||||
username string
|
||||
password string
|
||||
token string
|
||||
}
|
||||
|
||||
func newOptions(opts ...Option) (*options, error) {
|
||||
o := &options{}
|
||||
for _, opt := range opts {
|
||||
if err := opt(o); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return o, nil
|
||||
}
|
||||
|
||||
type Option func(o *options) error
|
||||
|
||||
func WithPasswordGrant(
|
||||
username string,
|
||||
password string,
|
||||
) Option {
|
||||
return func(o *options) error {
|
||||
o.deviceFlow = false
|
||||
o.username = username
|
||||
o.password = password
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func WithDeviceFlow() Option {
|
||||
return func(o *options) error {
|
||||
o.deviceFlow = true
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func WithToken(
|
||||
token string,
|
||||
) Option {
|
||||
return func(o *options) error {
|
||||
o.deviceFlow = false
|
||||
o.token = token
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -37,7 +37,7 @@ func (c *Client) CreatePeerInZone(zoneID uuid.UUID, deviceID uuid.UUID, endpoint
|
||||
return models.Peer{}, err
|
||||
}
|
||||
|
||||
res, err := c.do(r)
|
||||
res, err := c.client.Do(r)
|
||||
if err != nil {
|
||||
return models.Peer{}, err
|
||||
}
|
||||
@@ -67,7 +67,7 @@ func (c *Client) GetZonePeers(zoneID uuid.UUID) ([]models.Peer, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res, err := c.do(req)
|
||||
res, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ func (c *Client) GetCurrentUser() (models.User, error) {
|
||||
return models.User{}, err
|
||||
}
|
||||
|
||||
res, err := c.do(r)
|
||||
res, err := c.client.Do(r)
|
||||
if err != nil {
|
||||
return models.User{}, err
|
||||
}
|
||||
@@ -60,7 +60,7 @@ func (c *Client) MoveCurrentUserToZone(zoneID uuid.UUID) (models.User, error) {
|
||||
return models.User{}, err
|
||||
}
|
||||
|
||||
res, err := c.do(req)
|
||||
res, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
return models.User{}, err
|
||||
}
|
||||
|
||||
+34
-2
@@ -30,7 +30,7 @@ func (c *Client) CreateZone(name, description, cidr string, hubZone bool) (model
|
||||
return models.Zone{}, err
|
||||
}
|
||||
|
||||
res, err := c.do(req)
|
||||
res, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
return models.Zone{}, err
|
||||
}
|
||||
@@ -42,7 +42,7 @@ func (c *Client) CreateZone(name, description, cidr string, hubZone bool) (model
|
||||
}
|
||||
|
||||
if res.StatusCode != http.StatusCreated {
|
||||
return models.Zone{}, fmt.Errorf("failed to create the zone")
|
||||
return models.Zone{}, fmt.Errorf("failed to create the zone. %s", string(resBody))
|
||||
}
|
||||
|
||||
var data models.Zone
|
||||
@@ -52,3 +52,35 @@ func (c *Client) CreateZone(name, description, cidr string, hubZone bool) (model
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
// ListZone lists all zones
|
||||
func (c *Client) ListZones() ([]models.Zone, error) {
|
||||
dest := c.baseURL.JoinPath(ZONES).String()
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, dest, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
resBody, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if res.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("failed to create the zone: %d", res.StatusCode)
|
||||
}
|
||||
|
||||
var data []models.Zone
|
||||
if err := json.Unmarshal(resBody, &data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
@@ -14,10 +14,10 @@ func NewDatabase(
|
||||
user string,
|
||||
password string,
|
||||
dbname string,
|
||||
port int,
|
||||
port string,
|
||||
sslmode string,
|
||||
) (*gorm.DB, error) {
|
||||
dsn := fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=%d sslmode=%s",
|
||||
dsn := fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=%s sslmode=%s",
|
||||
host, user, password, dbname, port, sslmode)
|
||||
var db *gorm.DB
|
||||
connectDb := func() error {
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/redhat-et/apex/internal/models"
|
||||
)
|
||||
|
||||
@@ -37,7 +36,7 @@ func (suite *HandlerTestSuite) TestCreateGetDevice() {
|
||||
assert.NoError(err)
|
||||
|
||||
assert.Equal(newDevice.PublicKey, actual.PublicKey)
|
||||
assert.Equal(uuid.MustParse(TestUserID), actual.UserID)
|
||||
assert.Equal(TestUserID, actual.UserID)
|
||||
|
||||
_, res, err = suite.ServeRequest(
|
||||
http.MethodGet,
|
||||
|
||||
@@ -16,12 +16,7 @@ const AuthUserName string = "_apex.UserName"
|
||||
|
||||
func (api *API) CreateUserIfNotExists() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
userID := c.GetString(gin.AuthUserKey)
|
||||
id, err := uuid.Parse(userID)
|
||||
if err != nil {
|
||||
_ = c.AbortWithError(http.StatusBadRequest, fmt.Errorf("bad user id"))
|
||||
return
|
||||
}
|
||||
id := c.GetString(gin.AuthUserKey)
|
||||
userName := c.GetString(AuthUserName)
|
||||
var user models.User
|
||||
res := api.db.First(&user, "id = ?", id)
|
||||
@@ -33,7 +28,7 @@ func (api *API) CreateUserIfNotExists() gin.HandlerFunc {
|
||||
user.UserName = userName
|
||||
api.db.Create(&user)
|
||||
} else {
|
||||
_ = c.AbortWithError(http.StatusInternalServerError, fmt.Errorf("can't find record for user id %s", userID))
|
||||
_ = c.AbortWithError(http.StatusInternalServerError, fmt.Errorf("can't find record for user id %s", id))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
// Device is a unique, end-user device.
|
||||
type Device struct {
|
||||
Base
|
||||
UserID uuid.UUID `json:"user_id"`
|
||||
UserID string `json:"user_id" example:"694aa002-5d19-495e-980b-3d8fd508ea10"`
|
||||
PublicKey string `gorm:"uniqueIndex" json:"public_key"`
|
||||
Peers []*Peer `json:"-"`
|
||||
PeerList []uuid.UUID `gorm:"-" json:"peers" example:"97d5214a-8c51-4772-b492-53de034740c5"`
|
||||
|
||||
@@ -1,13 +1,19 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// User is the owner of a device, and a member of one Zone
|
||||
type User struct {
|
||||
Base
|
||||
// Since the ID comes from the IDP, we have no control over the format...
|
||||
ID string `gorm:"primary_key;" json:"id" example:"aa22666c-0f57-45cb-a449-16efecc04f2e"`
|
||||
CreatedAt time.Time `json:"-"`
|
||||
UpdatedAt time.Time `json:"-"`
|
||||
DeletedAt *time.Time `sql:"index" json:"-"`
|
||||
Devices []*Device `json:"-"`
|
||||
DeviceList []uuid.UUID `gorm:"-" json:"devices" example:"4902c991-3dd1-49a6-9f26-d82496c80aff"`
|
||||
ZoneID uuid.UUID `json:"zone_id" example:"94deb404-c4eb-4097-b59d-76b024ff7867"`
|
||||
@@ -21,7 +27,7 @@ func (u *User) BeforeCreate(tx *gorm.DB) error {
|
||||
if u.DeviceList == nil {
|
||||
u.DeviceList = make([]uuid.UUID, 0)
|
||||
}
|
||||
return u.Base.BeforeCreate(tx)
|
||||
return nil
|
||||
}
|
||||
|
||||
// PatchUser is used to update a user
|
||||
|
||||
@@ -1,97 +1,71 @@
|
||||
package routers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/MicahParks/keyfunc"
|
||||
"github.com/coreos/go-oidc"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// key for username in gin.Context
|
||||
const AuthUserName string = "_apex.UserName"
|
||||
|
||||
type KeyCloakAuth struct {
|
||||
jwks *keyfunc.JWKS
|
||||
}
|
||||
|
||||
type Claims struct {
|
||||
Scope string `json:"scope"`
|
||||
FullName string `json:"name"`
|
||||
UserName string `json:"preferred_username"`
|
||||
GivenName string `json:"given_name"`
|
||||
FamilyName string `json:"family_name"`
|
||||
jwt.RegisteredClaims
|
||||
Subject string `json:"sub"`
|
||||
}
|
||||
|
||||
func NewKeyCloakAuth(url string) (*KeyCloakAuth, error) {
|
||||
// Create the keyfunc options. Use an error handler that logs. Refresh the JWKS when a JWT signed by an unknown KID
|
||||
// is found or at the specified interval. Rate limit these refreshes. Timeout the initial JWKS refresh request after
|
||||
// 10 seconds. This timeout is also used to create the initial context.Context for keyfunc.Get.
|
||||
options := keyfunc.Options{
|
||||
RefreshErrorHandler: func(err error) {
|
||||
log.Printf("There was an error with the jwt.Keyfunc\nError: %s", err.Error())
|
||||
},
|
||||
RefreshInterval: time.Hour,
|
||||
RefreshRateLimit: time.Minute * 5,
|
||||
RefreshTimeout: time.Second * 10,
|
||||
RefreshUnknownKID: true,
|
||||
}
|
||||
|
||||
jwks, err := keyfunc.Get(url, options)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to create JWKS from resource at the given URL.\nError: %s", err.Error())
|
||||
}
|
||||
return &KeyCloakAuth{jwks: jwks}, nil
|
||||
}
|
||||
|
||||
func (a *KeyCloakAuth) AuthFunc() gin.HandlerFunc {
|
||||
// Naive JWS Key validation
|
||||
func ValidateJWT(verifier *oidc.IDTokenVerifier, clientIdWeb string, clientIdCli string) func(*gin.Context) {
|
||||
return func(c *gin.Context) {
|
||||
header := c.Request.Header.Get("Authorization")
|
||||
if header == "" {
|
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "no Authorization header present"})
|
||||
return
|
||||
}
|
||||
|
||||
jwtB64, ok := extractTokenFromAuthHeader(header)
|
||||
if !ok {
|
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "unable to get token from header"})
|
||||
return
|
||||
}
|
||||
|
||||
token, err := jwt.ParseWithClaims(jwtB64, &Claims{}, a.jwks.Keyfunc)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to parse the JWT. %s", err.Error())
|
||||
authz := c.Request.Header.Get("Authorization")
|
||||
if authz == "" {
|
||||
c.AbortWithStatus(http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
if !token.Valid {
|
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "token is not valid"})
|
||||
parts := strings.Split(authz, " ")
|
||||
if len(parts) != 2 {
|
||||
c.AbortWithStatus(http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
if claims, ok := token.Claims.(*Claims); ok {
|
||||
c.Set(gin.AuthUserKey, claims.Subject)
|
||||
c.Set(AuthUserName, claims.UserName)
|
||||
// c.Set(AuthUserScope, claims.Scope)
|
||||
} else {
|
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "unable to extract user info from claims"})
|
||||
if strings.ToLower(parts[0]) != "bearer" {
|
||||
c.AbortWithStatus(http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
log.Debug("verifying token")
|
||||
token, err := verifier.Verify(c.Request.Context(), parts[1])
|
||||
if err != nil {
|
||||
c.AbortWithStatus(http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
for _, audience := range token.Audience {
|
||||
if audience != clientIdWeb && audience != clientIdCli {
|
||||
c.AbortWithStatus(http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
log.Debug("getting claims")
|
||||
var claims Claims
|
||||
if err := token.Claims(&claims); err != nil {
|
||||
c.AbortWithStatus(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
log.Debugf("claims: %+v", claims)
|
||||
c.Set(gin.AuthUserKey, claims.Subject)
|
||||
c.Set(AuthUserName, claims.UserName)
|
||||
// c.Set(AuthUserScope, claims.Scope)
|
||||
log.Debugf("user-id is %s", claims.Subject)
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
func extractTokenFromAuthHeader(val string) (token string, ok bool) {
|
||||
authHeaderParts := strings.Split(val, " ")
|
||||
if len(authHeaderParts) != 2 || !strings.EqualFold(authHeaderParts[0], "bearer") {
|
||||
return "", false
|
||||
}
|
||||
return authHeaderParts[1], true
|
||||
}
|
||||
|
||||
+22
-54
@@ -1,74 +1,42 @@
|
||||
package routers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/cenkalti/backoff"
|
||||
"github.com/gin-contrib/cors"
|
||||
"github.com/coreos/go-oidc"
|
||||
"github.com/gin-gonic/gin"
|
||||
_ "github.com/redhat-et/apex/internal/docs"
|
||||
"github.com/redhat-et/apex/internal/handlers"
|
||||
log "github.com/sirupsen/logrus"
|
||||
swaggerFiles "github.com/swaggo/files"
|
||||
ginSwagger "github.com/swaggo/gin-swagger"
|
||||
)
|
||||
|
||||
func NewRouter(api *handlers.API, keycloakAddress string) (*gin.Engine, error) {
|
||||
func NewAPIRouter(
|
||||
ctx context.Context,
|
||||
api *handlers.API,
|
||||
clientIdWeb string,
|
||||
clientIdCli string,
|
||||
oidcURL string) (*gin.Engine, error) {
|
||||
r := gin.Default()
|
||||
|
||||
log.Debug("Waiting for Keycloak")
|
||||
connectKeycloak := func() error {
|
||||
res, err := http.Get(fmt.Sprintf("http://%s:8080/auth/health/ready", keycloakAddress))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
body, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var response map[string]interface{}
|
||||
if err := json.Unmarshal(body, &response); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, ok := response["status"]; !ok {
|
||||
return fmt.Errorf("no status")
|
||||
}
|
||||
|
||||
if response["status"] != "UP" {
|
||||
return fmt.Errorf("not ready")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
err := backoff.Retry(connectKeycloak, backoff.NewExponentialBackOff())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
jwksURL := fmt.Sprintf("http://%s:8080/auth/realms/controller/protocol/openid-connect/certs", keycloakAddress)
|
||||
|
||||
auth, err := NewKeyCloakAuth(jwksURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
corsConfig := cors.DefaultConfig()
|
||||
corsConfig.AllowAllOrigins = true
|
||||
r.Use(cors.New(corsConfig))
|
||||
|
||||
r.GET("/api/health", func(c *gin.Context) {
|
||||
r.GET("/health", func(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"message": "ok"})
|
||||
})
|
||||
|
||||
private := r.Group("/api")
|
||||
provider, err := oidc.NewProvider(ctx, oidcURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
config := &oidc.Config{
|
||||
// Client ID checks are skipped since we perform these later
|
||||
// in the ValidateJWT function
|
||||
SkipClientIDCheck: true,
|
||||
}
|
||||
verifier := provider.Verifier(config)
|
||||
|
||||
private := r.Group("/")
|
||||
{
|
||||
private.Use(auth.AuthFunc())
|
||||
private.Use(ValidateJWT(verifier, clientIdWeb, clientIdCli))
|
||||
private.Use(api.CreateUserIfNotExists())
|
||||
// Zones
|
||||
private.GET("/zones", api.ListZones)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,4 +0,0 @@
|
||||
VITE_KEYCLOAK_URL=/auth
|
||||
VITE_KEYCLOAK_REALM=controller
|
||||
VITE_KEYCLOAK_CLIENT_ID=front-controller
|
||||
VITE_CONTROLLER_URL=/api
|
||||
+18
-50
@@ -1,19 +1,10 @@
|
||||
import { useState, useRef, useEffect } from 'react';
|
||||
import {
|
||||
Admin,
|
||||
AuthProvider,
|
||||
DataProvider,
|
||||
Resource,
|
||||
fetchUtils
|
||||
} from 'react-admin';
|
||||
import simpleRestProvider from 'ra-data-simple-rest';
|
||||
|
||||
// Auth
|
||||
import Keycloak, {
|
||||
KeycloakConfig,
|
||||
KeycloakTokenParsed,
|
||||
KeycloakInitOptions,
|
||||
} from 'keycloak-js';
|
||||
import { keycloakAuthProvider, httpClient } from 'ra-keycloak';
|
||||
import { goOidcAgentAuthProvider } from './providers/AuthProvider';
|
||||
|
||||
// icons
|
||||
import DeviceIcon from '@mui/icons-material/Devices';
|
||||
@@ -26,56 +17,33 @@ import { PeerShow, PeerList } from "./pages/Peers";
|
||||
import { UserShow, UserList } from "./pages/Users";
|
||||
import { DeviceList, DeviceShow } from "./pages/Devices";
|
||||
import Dashboard from './pages/Dashboard';
|
||||
import LoginPage from './pages/Login';
|
||||
import Layout from "./layout/Layout";
|
||||
|
||||
const config : KeycloakConfig = {
|
||||
url: import.meta.env.VITE_KEYCLOAK_URL,
|
||||
realm: import.meta.env.VITE_KEYCLOAK_REALM,
|
||||
clientId: import.meta.env.VITE_KEYCLOAK_CLIENT_ID,
|
||||
const fetchJson = (url: URL, options: any = {}) => {
|
||||
// Includes the encrypted session cookie in requests to the API
|
||||
options.credentials = "include";
|
||||
return fetchUtils.fetchJson(url, options);
|
||||
};
|
||||
|
||||
const initOptions: KeycloakInitOptions = { onLoad: 'login-required' };
|
||||
|
||||
const getPermissions = (decoded: KeycloakTokenParsed) => {
|
||||
const roles = decoded?.realm_access?.roles;
|
||||
if (!roles) {
|
||||
return false;
|
||||
}
|
||||
if (roles.includes('admin')) return 'admin';
|
||||
if (roles.includes('user')) return 'user';
|
||||
return false;
|
||||
};
|
||||
const backend = `${window.location.protocol}//api.${window.location.host}`;
|
||||
const authProvider = goOidcAgentAuthProvider(backend);
|
||||
const dataProvider = simpleRestProvider(
|
||||
`${backend}/api`,
|
||||
fetchJson,
|
||||
'X-Total-Count',
|
||||
);
|
||||
|
||||
const App = () => {
|
||||
const [keycloak, setKeycloak] = useState<Keycloak | undefined>(undefined);
|
||||
const authProvider = useRef<AuthProvider | undefined>(undefined);
|
||||
const dataProvider = useRef<DataProvider | undefined>(undefined);
|
||||
|
||||
useEffect(() => {
|
||||
const initKeyCloakClient = async () => {
|
||||
const keycloakClient = new Keycloak(config);
|
||||
await keycloakClient.init(initOptions);
|
||||
authProvider.current = keycloakAuthProvider(keycloakClient, {
|
||||
onPermissions: getPermissions,
|
||||
});
|
||||
dataProvider.current = simpleRestProvider(import.meta.env.VITE_CONTROLLER_URL, httpClient(keycloakClient), 'X-Total-Count');
|
||||
setKeycloak(keycloakClient);
|
||||
};
|
||||
if (!keycloak) {
|
||||
initKeyCloakClient();
|
||||
}
|
||||
}, [keycloak]);
|
||||
|
||||
// hide the admin until the dataProvider and authProvider are ready
|
||||
if (!keycloak) return <p>Loading...</p>;
|
||||
|
||||
return (
|
||||
<Admin
|
||||
dashboard={Dashboard}
|
||||
authProvider={authProvider.current}
|
||||
dataProvider={dataProvider.current}
|
||||
authProvider={authProvider}
|
||||
dataProvider={dataProvider}
|
||||
title="Controller"
|
||||
layout={Layout}
|
||||
loginPage={LoginPage}
|
||||
requireAuth
|
||||
>
|
||||
<Resource name="users" list={UserList} show={UserShow} icon={UserIcon} recordRepresentation={(record) => `${record.username}`} />
|
||||
<Resource name="devices" list={DeviceList} show={DeviceShow} icon={DeviceIcon} recordRepresentation={(record) => `${record.hostname}`} />
|
||||
|
||||
Vendored
-10
@@ -1,10 +0,0 @@
|
||||
interface ImportMetaEnv {
|
||||
readonly VITE_KEYCLOAK_URL: string
|
||||
readonly VITE_KEYCLOAK_REALM: string
|
||||
readonly VITE_KEYCLOAK_CLIENT_ID: string
|
||||
readonly VITE_CONTROLLER_URL: string
|
||||
}
|
||||
|
||||
interface ImportMeta {
|
||||
readonly env: ImportMetaEnv
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { styled } from "@mui/material/styles";
|
||||
import { useLogin, Form, Login, LoginFormProps } from "react-admin";
|
||||
import { CardContent, Button, CircularProgress } from "@mui/material";
|
||||
|
||||
const LoginForm = (props: LoginFormProps) => {
|
||||
const { redirectTo, className } = props;
|
||||
const [loading, setLoading] = useState(false);
|
||||
const login = useLogin();
|
||||
|
||||
useEffect(() => {
|
||||
const { searchParams } = new URL(window.location.href);
|
||||
const code = searchParams.get("code");
|
||||
const state = searchParams.get("state");
|
||||
|
||||
// If code is present, we came back from the provider
|
||||
if (code && state) {
|
||||
console.log("handling return from login");
|
||||
setLoading(true);
|
||||
login({ code, state });
|
||||
}
|
||||
}, [login]);
|
||||
|
||||
const handleLogin = () => {
|
||||
console.log("login button pressed");
|
||||
setLoading(true);
|
||||
login({}); // Do not provide code, just trigger the redirection
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledForm
|
||||
onSubmit={handleLogin}
|
||||
mode="onChange"
|
||||
noValidate
|
||||
className={className}
|
||||
>
|
||||
<CardContent className={LoginFormClasses.content}>
|
||||
<Button
|
||||
variant="contained"
|
||||
type="submit"
|
||||
color="primary"
|
||||
disabled={loading}
|
||||
fullWidth
|
||||
className={LoginFormClasses.button}
|
||||
>
|
||||
{loading && <CircularProgress size={18} thickness={2} />}
|
||||
Login
|
||||
</Button>
|
||||
</CardContent>
|
||||
</StyledForm>
|
||||
);
|
||||
};
|
||||
|
||||
const PREFIX = "RaLoginForm";
|
||||
|
||||
export const LoginFormClasses = {
|
||||
content: `${PREFIX}-content`,
|
||||
button: `${PREFIX}-button`,
|
||||
icon: `${PREFIX}-icon`,
|
||||
};
|
||||
|
||||
const StyledForm = styled(Form, {
|
||||
name: PREFIX,
|
||||
overridesResolver: (props, styles) => styles.root,
|
||||
})(({ theme }) => ({
|
||||
[`& .${LoginFormClasses.content}`]: {
|
||||
width: 300,
|
||||
},
|
||||
[`& .${LoginFormClasses.button}`]: {
|
||||
marginTop: theme.spacing(2),
|
||||
},
|
||||
[`& .${LoginFormClasses.icon}`]: {
|
||||
margin: theme.spacing(0.3),
|
||||
},
|
||||
}));
|
||||
|
||||
const LoginPage = () => (
|
||||
<Login
|
||||
backgroundImage="https://source.unsplash.com/9wg5jCEPBsw"
|
||||
children={<LoginForm />}
|
||||
/>
|
||||
);
|
||||
|
||||
export default LoginPage;
|
||||
@@ -0,0 +1,128 @@
|
||||
import { AuthProvider, UserIdentity } from "react-admin";
|
||||
|
||||
const cleanup = () => {
|
||||
// Remove the ?code&state from the URL
|
||||
window.history.replaceState(
|
||||
{},
|
||||
window.document.title,
|
||||
window.location.origin
|
||||
);
|
||||
};
|
||||
|
||||
export const goOidcAgentAuthProvider = (api: string): AuthProvider => ({
|
||||
login: async (params = {}) => {
|
||||
console.log("login");
|
||||
// 1. Redirect to the issuer to ask authentication
|
||||
if (!params.code || !params.state) {
|
||||
const request = new Request(`${api}/login/start`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
});
|
||||
try {
|
||||
const response = await fetch(request);
|
||||
const data = await response.json();
|
||||
if (response && data) {
|
||||
window.location.replace(data.authorization_request_url);
|
||||
}
|
||||
} catch (err: any) {
|
||||
throw new Error("Network error");
|
||||
}
|
||||
}
|
||||
|
||||
// 2. We came back from the issuer with ?code infos in query params
|
||||
const request = new Request(`${api}/login/end`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ request_url: window.location.href }),
|
||||
});
|
||||
try {
|
||||
const response = await fetch(request);
|
||||
const data = await response.json();
|
||||
if (response && data) {
|
||||
cleanup();
|
||||
return data.handled && data.logged_in
|
||||
? Promise.resolve()
|
||||
: Promise.reject();
|
||||
}
|
||||
} catch (err: any) {
|
||||
cleanup();
|
||||
throw new Error(err.statusText);
|
||||
}
|
||||
},
|
||||
logout: async () => {
|
||||
console.log("logout");
|
||||
const request = new Request(`${api}/logout`, {
|
||||
method: "post",
|
||||
credentials: "include",
|
||||
});
|
||||
try {
|
||||
const response = await fetch(request);
|
||||
if (response.status === 401) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
const data = await response.json();
|
||||
if (response && data) {
|
||||
window.location.replace(data.logout_url);
|
||||
}
|
||||
} catch (err: any) {
|
||||
throw new Error(err.statusText);
|
||||
}
|
||||
},
|
||||
checkError: async (error: any) => {
|
||||
console.log("checkError");
|
||||
const status = error.status;
|
||||
if (status === 401) {
|
||||
return Promise.reject();
|
||||
}
|
||||
return Promise.resolve();
|
||||
},
|
||||
checkAuth: async () => {
|
||||
console.log("checkAuth");
|
||||
console.log(document.cookie);
|
||||
const request = new Request(`${api}/login/end`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ request_url: window.location.href }),
|
||||
});
|
||||
try {
|
||||
const response = await fetch(request);
|
||||
const data = await response.json();
|
||||
if (response && data) {
|
||||
return data.logged_in ? Promise.resolve() : Promise.reject();
|
||||
}
|
||||
} catch (err: any) {
|
||||
throw new Error(err.statusText);
|
||||
}
|
||||
},
|
||||
getPermissions: async () => {
|
||||
console.log("getPermissions");
|
||||
// TODO: Add a callback so people can decode the claims
|
||||
return Promise.resolve();
|
||||
},
|
||||
getIdentity: async (): Promise<UserIdentity> => {
|
||||
console.log("getIdentity");
|
||||
const request = new Request(`${api}/user_info`, {
|
||||
credentials: "include",
|
||||
});
|
||||
var id;
|
||||
try {
|
||||
const response = await fetch(request);
|
||||
const data = await response.json();
|
||||
if (response && data) {
|
||||
id = {
|
||||
id: data.subject,
|
||||
fullName: data.preferred_username,
|
||||
avatar: data.picture,
|
||||
} as UserIdentity;
|
||||
}
|
||||
} catch (err: any) {
|
||||
throw new Error(err.statusText);
|
||||
}
|
||||
if (id) {
|
||||
return Promise.resolve(id);
|
||||
}
|
||||
return Promise.reject();
|
||||
},
|
||||
});
|
||||
@@ -485,6 +485,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.2.0.tgz#76467a94aa888aeb22aafa43eb6ff889df3a5a7f"
|
||||
integrity sha512-rBevIsj2nclStJ7AxTdfsa3ovHb1H+qApwrxcTVo+NNdeJiB9V75hsKfrkG5AwNcRUNxrPPiScGYCNmLMoh8pg==
|
||||
|
||||
"@fortawesome/fontawesome-common-types@6.2.1":
|
||||
version "6.2.1"
|
||||
resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.2.1.tgz#411e02a820744d3f7e0d8d9df9d82b471beaa073"
|
||||
integrity sha512-Sz07mnQrTekFWLz5BMjOzHl/+NooTdW8F8kDQxjWwbpOJcnoSg4vUDng8d/WR1wOxM0O+CY9Zw0nR054riNYtQ==
|
||||
|
||||
"@fortawesome/fontawesome-svg-core@~1 || ~6":
|
||||
version "6.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.2.0.tgz#11856eaf4dd1d865c442ddea1eed8ee855186ba2"
|
||||
@@ -499,6 +504,13 @@
|
||||
dependencies:
|
||||
"@fortawesome/fontawesome-common-types" "6.2.0"
|
||||
|
||||
"@fortawesome/free-solid-svg-icons@^6.2.0":
|
||||
version "6.2.1"
|
||||
resolved "https://registry.yarnpkg.com/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.2.1.tgz#2290ea5adcf1537cbd0c43de6feb38af02141d27"
|
||||
integrity sha512-oKuqrP5jbfEPJWTij4sM+/RvgX+RMFwx3QZCZcK9PrBDgxC35zuc7AOFsyMjMd/PIFPeB2JxyqDr5zs/DZFPPw==
|
||||
dependencies:
|
||||
"@fortawesome/fontawesome-common-types" "6.2.1"
|
||||
|
||||
"@fortawesome/react-fontawesome@^0.2.0":
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@fortawesome/react-fontawesome/-/react-fontawesome-0.2.0.tgz#d90dd8a9211830b4e3c08e94b63a0ba7291ddcf4"
|
||||
|
||||
Reference in New Issue
Block a user