Skip to content

Commit 24fbe78

Browse files
n3rdc4ptnmaximilianbraundattito
committed
Initial open-source commit
Co-authored-by: Maximilian Braun <maximilian.braun@sap.com> Co-authored-by: David Siregar <david.siregar@sap.com>
1 parent 509aa78 commit 24fbe78

25 files changed

+1866
-33
lines changed

.dockerignore

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
main
2+
kubeconfig.yaml
3+
kube-api-reflection
4+
.null-ls*

.github/workflows/build.yml

+79
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
name: Build
2+
on:
3+
push:
4+
branches:
5+
- main
6+
tags:
7+
- "*"
8+
9+
pull_request:
10+
branches:
11+
- main
12+
13+
workflow_dispatch:
14+
15+
env:
16+
DOCKER_IMAGE: ghcr.io/${{ github.repository }}
17+
18+
jobs:
19+
build:
20+
runs-on: ubuntu-latest
21+
permissions:
22+
id-token: write
23+
packages: write
24+
contents: read
25+
attestations: write
26+
27+
steps:
28+
- uses: actions/checkout@v4
29+
30+
- name: Docker meta
31+
id: meta
32+
uses: docker/metadata-action@v5
33+
with:
34+
# list of Docker images to use as base name for tags
35+
images: |
36+
${{ env.DOCKER_IMAGE }}
37+
# generate Docker tags based on the following events/attributes
38+
tags: |
39+
type=schedule
40+
type=ref,event=branch
41+
type=ref,event=pr
42+
type=semver,pattern={{version}}
43+
type=semver,pattern={{major}}.{{minor}}
44+
type=semver,pattern={{major}}
45+
type=raw,value=latest,enable={{is_default_branch}}
46+
47+
- name: Login to Docker Registry
48+
if: github.event_name != 'pull_request'
49+
uses: docker/login-action@v3
50+
with:
51+
registry: ghcr.io
52+
username: ${{ github.repository_owner }}
53+
password: ${{ secrets.GITHUB_TOKEN }}
54+
55+
- name: Set up QEMU
56+
uses: docker/setup-qemu-action@v3
57+
58+
- name: Set up Docker Buildx
59+
uses: docker/setup-buildx-action@v3
60+
61+
- name: Build and push
62+
uses: docker/build-push-action@v6
63+
with:
64+
context: .
65+
platforms: linux/amd64,linux/arm64
66+
push: ${{ github.event_name != 'pull_request' }}
67+
tags: ${{ steps.meta.outputs.tags }}
68+
labels: ${{ steps.meta.outputs.labels }}
69+
cache-from: type=gha
70+
cache-to: type=gha,mode=max
71+
72+
# Only for public repositories
73+
# - name: Attest
74+
# uses: actions/attest-build-provenance@v1
75+
# id: attest
76+
# with:
77+
# subject-name: ghcr.io/${{ github.repository }}
78+
# subject-digest: ${{ steps.push.outputs.digest }}
79+
# push-to-registry: true

.gitignore

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
.idea
2+
main
3+
kubeconfig.yaml
4+
kube-api-reflection
5+
.null-ls*
6+
tmp
7+
.env
8+
9+
coverage.html
10+
coverage.out

.reuse/dep5

-29
This file was deleted.

Dockerfile

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
FROM golang:1.23-bookworm AS build
2+
3+
WORKDIR /app
4+
5+
COPY go.mod ./
6+
COPY go.sum ./
7+
RUN go mod download
8+
9+
COPY . ./
10+
11+
RUN CGO_ENABLED=0 go build -o /bin/app cmd/server/main.go
12+
13+
## Deploy
14+
FROM scratch
15+
16+
WORKDIR /
17+
18+
COPY --from=build /bin/app /bin/app
19+
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
20+
21+
EXPOSE 3000
22+
23+
ENTRYPOINT ["/bin/app"]

Makefile

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
GO := go
2+
PKG := ./...
3+
COVERAGE_FILE := coverage.out
4+
COVERAGE_HTML := coverage.html
5+
6+
# Default goal
7+
.PHONY: all
8+
all: clean build test
9+
10+
# Build the project
11+
.PHONY: build
12+
build:
13+
$(GO) build -v $(PKG)
14+
15+
# Run tests with coverage
16+
.PHONY: test
17+
test:
18+
$(GO) test -v -coverprofile=$(COVERAGE_FILE) $(PKG)
19+
$(GO) tool cover -html=$(COVERAGE_FILE) -o $(COVERAGE_HTML)
20+
@echo "Coverage report generated at $(COVERAGE_HTML)"
21+
22+
# Clean up generated files
23+
.PHONY: clean
24+
clean:
25+
$(GO) clean
26+
rm -f $(COVERAGE_FILE) $(COVERAGE_HTML)
27+
28+
# Format the code
29+
.PHONY: fmt
30+
fmt:
31+
$(GO) fmt $(PKG)
32+
33+
# Lint the code
34+
.PHONY: lint
35+
lint:
36+
golangci-lint run
37+
38+
# Run all checks
39+
.PHONY: check
40+
check: fmt lint test
41+
42+
# Install dependencies
43+
.PHONY: deps
44+
deps:
45+
$(GO) mod tidy
46+
$(GO) mod vendor
47+
48+
.PHONY: start
49+
start:
50+
$(GO) run cmd/server/main.go

README.md

+60-4
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,76 @@
1-
[![REUSE status](https://api.reuse.software/badge/github.com/openmcp-project/ui-backend)](https://api.reuse.software/info/github.com/openmcp-project/ui-backend)
2-
31
# ui-backend
42

3+
[![REUSE status](https://api.reuse.software/badge/github.com/openmcp-project/ui-backend)](https://api.reuse.software/info/github.com/openmcp-project/ui-backend)
4+
55
## About this project
66

7-
UI backend for @openmcp-project
7+
This is the backend for our [MCP UI](https://github.com/openmcp-project/ui-frontend).
8+
Its a simple proxy server which sits between the UI frontend and the Kubernetes API server.
9+
10+
### Motivation
11+
12+
We want to call the kubernetes api server directly from the browser, but we have several problems preventing us from calling the api from the browser:
13+
14+
- TLS certificate is not signed from a well-known CA
15+
- CORS is not configured most of the time
16+
17+
### Solution
18+
19+
The `ui-backend` server acts like a proxy when talking to the Crate-Cluster or MCPs from the browser.
20+
The browser sends the request to the `ui-backend`, with authorization data and optionally the project, workspace and controlplane name of the MCP in header data.
21+
22+
- If requesting the Crate: The request will get send to the crate cluster with the authorization data in the headers
23+
- If requesting an MCP: The `ui-backend` will call the Crate to get the `kubeconfig` of the MCP and then calls the MCP with that kubeconfig
24+
25+
There are only some modifications done when piping the request to the api server, preventing some headers from going through.
826

927
## Requirements and Setup
1028

11-
*Insert a short description what is required to get your project running...*
29+
You need to have a running mcp landscape. Then reference the KUBECONFIG for the backend using the `KUBECONFIG` environment variable.
30+
31+
The backend can be started using:
32+
33+
```bash
34+
go run cmd/server/main.go
35+
```
36+
37+
## Usage
38+
39+
You can reach the backend on port `3000` and the path as you would directly to the api server.
40+
41+
```txt
42+
For example: http://localhost:3000/api/v1/namespaces
43+
```
44+
45+
Put the authorization data in the following headers:
46+
47+
- `X-Client-Certificate-Data`
48+
- `X-Client-Key-Data`
49+
50+
or (for OIDC):
51+
52+
- `Authorization: <token>`
53+
54+
Also configure the api-server you want to call:
55+
56+
- Crate: Add the header `X-Use-Crate-Cluster: true`
57+
- MCP: Add the headers `X-Project-Name`, `X-Workspace-Name` and `X-Control-Plane-Name`
58+
59+
### Parsing JSON
60+
61+
`ui-backend` support jsonpath (kubectl version) and jq (gojq) to parse json before sending it to the client, reducing the data transfered to the client.
62+
63+
Usage:
64+
65+
- JsonPath: Add a header `X-jsonpath` with the jsonpath query
66+
- JQ: Add a header `X-jq` with the jq query
1267

1368
## Support, Feedback, Contributing
1469

1570
This project is open to feature requests/suggestions, bug reports etc. via [GitHub issues](https://github.com/openmcp-project/ui-backend/issues). Contribution and feedback are encouraged and always welcome. For more information about how to contribute, the project structure, as well as additional contribution information, see our [Contribution Guidelines](CONTRIBUTING.md).
1671

1772
## Security / Disclosure
73+
1874
If you find any bug that may be a security problem, please follow our instructions at [in our security policy](https://github.com/openmcp-project/ui-backend/security/policy) on how to report it. Please do not create GitHub issues for security-related doubts or problems.
1975

2076
## Code of Conduct

REUSE.toml

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
version = 1
2+
SPDX-PackageName = "ui-backend"
3+
SPDX-PackageDownloadLocation = "https://github.com/openmcp-project/ui-backend"
4+
SPDX-PackageComment = "The code in this project may include calls to APIs (\"API Calls\") of\n SAP or third-party products or services developed outside of this project\n (\"External Products\").\n \"APIs\" means application programming interfaces, as well as their respective\n specifications and implementing code that allows software to communicate with\n other software.\n API Calls to External Products are not licensed under the open source license\n that governs this project. The use of such API Calls and related External\n Products are subject to applicable additional agreements with the relevant\n provider of the External Products. In no event shall the open source license\n that governs this project grant any rights in or to any External Products, or\n alter, expand or supersede any terms of the applicable additional agreements.\n If you have a valid license agreement with SAP for the use of a particular SAP\n External Product, then you may make use of any API Calls included in this\n project's code for that SAP External Product, subject to the terms of such\n license agreement. If you do not have a valid license agreement for the use of\n a particular SAP External Product, then you may only make use of any API Calls\n in this project for that SAP External Product for your internal, non-productive\n and non-commercial test and evaluation of such API Calls. Nothing herein grants\n you any rights to use or access any SAP External Product, or provide any third\n parties the right to use of access any SAP External Product, through API Calls."
5+
6+
[[annotations]]
7+
path = "**"
8+
precedence = "closest"
9+
SPDX-FileCopyrightText = "2025 SAP SE or an SAP affiliate company and ui-backend contributors"
10+
SPDX-License-Identifier = "Apache-2.0"

cmd/server/main.go

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"log/slog"
6+
"net/http"
7+
"os"
8+
"time"
9+
10+
"github.com/openmcp-project/ui-backend/internal/utils"
11+
"github.com/openmcp-project/ui-backend/pkg/k8s"
12+
"k8s.io/client-go/tools/clientcmd"
13+
14+
"github.com/openmcp-project/ui-backend/internal/server"
15+
)
16+
17+
func main() {
18+
ctx := context.Background()
19+
20+
slog.SetDefault(slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelDebug})))
21+
22+
kubeconfigPath := os.Getenv(clientcmd.RecommendedConfigPathEnvVar)
23+
if kubeconfigPath == "" {
24+
slog.Error("env variable '%s' with kubeconfig path not set", clientcmd.RecommendedConfigPathEnvVar)
25+
return
26+
}
27+
go utils.StartListeningOnKubeconfig(ctx, kubeconfigPath)
28+
29+
cachingKube := k8s.NewCachingKube(k8s.HttpKube{}, time.Second*30, time.Minute)
30+
downstreamKube := k8s.HttpKube{}
31+
32+
mux := server.NewMiddleware(cachingKube, downstreamKube)
33+
34+
address := ":3000"
35+
slog.Info("Starting server", "address", address)
36+
if err := http.ListenAndServe(address, mux); err != nil {
37+
slog.Error("failed to start server", "err", err)
38+
}
39+
}

go.mod

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
module github.com/openmcp-project/ui-backend
2+
3+
go 1.23.0
4+
5+
toolchain go1.24.0
6+
7+
require (
8+
github.com/fsnotify/fsnotify v1.8.0
9+
github.com/itchyny/gojq v0.12.17
10+
github.com/patrickmn/go-cache v2.1.0+incompatible
11+
gopkg.in/yaml.v3 v3.0.1
12+
k8s.io/api v0.32.1
13+
k8s.io/apimachinery v0.32.1
14+
k8s.io/client-go v0.32.1
15+
)
16+
17+
require (
18+
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
19+
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
20+
github.com/go-logr/logr v1.4.2 // indirect
21+
github.com/gogo/protobuf v1.3.2 // indirect
22+
github.com/golang/protobuf v1.5.4 // indirect
23+
github.com/google/gofuzz v1.2.0 // indirect
24+
github.com/imdario/mergo v0.3.6 // indirect
25+
github.com/itchyny/timefmt-go v0.1.6 // indirect
26+
github.com/json-iterator/go v1.1.12 // indirect
27+
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
28+
github.com/modern-go/reflect2 v1.0.2 // indirect
29+
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
30+
github.com/spf13/pflag v1.0.5 // indirect
31+
github.com/x448/float16 v0.8.4 // indirect
32+
golang.org/x/net v0.30.0 // indirect
33+
golang.org/x/oauth2 v0.23.0 // indirect
34+
golang.org/x/sys v0.26.0 // indirect
35+
golang.org/x/term v0.25.0 // indirect
36+
golang.org/x/text v0.19.0 // indirect
37+
golang.org/x/time v0.7.0 // indirect
38+
google.golang.org/appengine v1.6.7 // indirect
39+
google.golang.org/protobuf v1.35.1 // indirect
40+
gopkg.in/inf.v0 v0.9.1 // indirect
41+
gopkg.in/yaml.v2 v2.4.0 // indirect
42+
k8s.io/klog/v2 v2.130.1 // indirect
43+
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect
44+
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect
45+
sigs.k8s.io/structured-merge-diff/v4 v4.4.2 // indirect
46+
sigs.k8s.io/yaml v1.4.0 // indirect
47+
)

0 commit comments

Comments
 (0)