mirror of
https://github.com/cozystack/cozystack.git
synced 2026-03-06 15:08:53 +00:00
Compare commits
15 Commits
v0.41.5
...
release-0.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9c4507b58a | ||
|
|
b82604718c | ||
|
|
82435cda9c | ||
|
|
fc5de97dc0 | ||
|
|
85b8ad3984 | ||
|
|
d1746a5f81 | ||
|
|
d94f5c24b4 | ||
|
|
3595eaea13 | ||
|
|
37e84be573 | ||
|
|
7e58850fb1 | ||
|
|
ef00445322 | ||
|
|
8bd3861164 | ||
|
|
d11dc983b8 | ||
|
|
5e9bff0e50 | ||
|
|
cfbe41d8e0 |
@@ -1 +1 @@
|
||||
ghcr.io/cozystack/cozystack/cluster-autoscaler:0.0.0@sha256:6f2b1d6b0b2bdc66f1cbb30c59393369cbf070cb8f5fec748f176952273483cc
|
||||
ghcr.io/cozystack/cozystack/cluster-autoscaler:0.0.0@sha256:598331326f0c2aac420187f0cc3a49fedcb22ed5de4afe50c6ccf8e05d9fa537
|
||||
|
||||
@@ -1 +1 @@
|
||||
ghcr.io/cozystack/cozystack/kubevirt-csi-driver:0.0.0@sha256:15b94dca216b73336e7f39f4ea1b76b7656890d6be8a8cf0d9a786b4006781f9
|
||||
ghcr.io/cozystack/cozystack/kubevirt-csi-driver:0.0.0@sha256:0b208ed506dd8c453426761d93ec3d42c9d1b791ba6c91b01c6386dcb1b02442
|
||||
|
||||
@@ -292,6 +292,12 @@ metadata:
|
||||
{{- end }}
|
||||
spec:
|
||||
clusterName: {{ $.Release.Name }}
|
||||
replicas: 2
|
||||
strategy:
|
||||
rollingUpdate:
|
||||
maxSurge: {{ $group.maxReplicas }}
|
||||
maxUnavailable: 1
|
||||
type: RollingUpdate
|
||||
selector:
|
||||
matchLabels:
|
||||
cluster.x-k8s.io/cluster-name: {{ $.Release.Name }}
|
||||
@@ -326,6 +332,7 @@ metadata:
|
||||
namespace: {{ $.Release.Namespace }}
|
||||
spec:
|
||||
clusterName: {{ $.Release.Name }}
|
||||
maxUnhealthy: 0
|
||||
nodeStartupTimeout: 10m
|
||||
selector:
|
||||
matchLabels:
|
||||
|
||||
@@ -1 +1 @@
|
||||
ghcr.io/cozystack/cozystack/mariadb-backup:0.0.0@sha256:aca403030ff5d831415d72367866fdf291fab73ee2cfddbe4c93c2915a316ab1
|
||||
ghcr.io/cozystack/cozystack/mariadb-backup:0.0.0@sha256:0ddbbec0568dcb9fbc317cd9cc654e826dbe88ba3f184fa9b6b58aacb93b4570
|
||||
|
||||
@@ -28,7 +28,7 @@ RUN go mod download
|
||||
|
||||
FROM alpine:3.22
|
||||
|
||||
RUN wget -O- https://github.com/cozystack/cozyhr/raw/refs/heads/main/hack/install.sh | sh -s -- -v 1.5.0
|
||||
RUN wget -O- https://github.com/cozystack/cozyhr/raw/refs/heads/main/hack/install.sh | sh -s -- -v 1.6.1
|
||||
|
||||
RUN apk add --no-cache make kubectl helm coreutils git jq openssl
|
||||
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
cozystack:
|
||||
image: ghcr.io/cozystack/cozystack/installer:v0.40.3@sha256:4581c0a80fd31db2988411d96afc77afd315726348468e495c9f5c21b2b2a664
|
||||
image: ghcr.io/cozystack/cozystack/installer:v0.40.7@sha256:6cf3f2439acab9599c00c788f8aabeaf4b4f515a4534bc21e20c95facb542e27
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
assets:
|
||||
image: ghcr.io/cozystack/cozystack/cozystack-assets:v0.40.3@sha256:48e63ec92d473038e29ea2375f57a449ed9b9295aced53ae27d3944507c076c4
|
||||
image: ghcr.io/cozystack/cozystack/cozystack-assets:v0.40.7@sha256:2409c3ce21fa43b7b864564be0a8b7249603b1caa506020db0d13dd7dfa38b8c
|
||||
|
||||
@@ -3,7 +3,7 @@ FROM ubuntu:22.04
|
||||
ARG KUBECTL_VERSION=1.33.2
|
||||
ARG TALOSCTL_VERSION=1.10.4
|
||||
ARG HELM_VERSION=3.18.3
|
||||
ARG COZYHR_VERSION=1.5.0
|
||||
ARG COZYHR_VERSION=1.6.1
|
||||
|
||||
ARG TARGETOS
|
||||
ARG TARGETARCH
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
e2e:
|
||||
image: ghcr.io/cozystack/cozystack/e2e-sandbox:v0.40.3@sha256:fde7616aacaf5939388bbe74eb6d946147ce855b9cceb47092f620b75ba2c98a
|
||||
image: ghcr.io/cozystack/cozystack/e2e-sandbox:v0.40.7@sha256:eac71ef0de3450fce96255629e77903630c63ade62b81e7055f1a689f92ee153
|
||||
|
||||
@@ -1 +1 @@
|
||||
ghcr.io/cozystack/cozystack/matchbox:v0.40.3@sha256:d9674696eb6c59e4564127bb919582ea66bf5b267504ce11ef4f9431db3a247f
|
||||
ghcr.io/cozystack/cozystack/matchbox:v0.40.7@sha256:55f1c6a324dc60d3a813777e26135110891a71b1643be6470fb7cd6b5c91d9e5
|
||||
|
||||
@@ -1 +1 @@
|
||||
ghcr.io/cozystack/cozystack/objectstorage-sidecar:v0.40.3@sha256:d342ac197b97b81ec42712ecd9d762c6c7710a401736e9d09c64a5b8d59774cc
|
||||
ghcr.io/cozystack/cozystack/objectstorage-sidecar:v0.40.7@sha256:3e61975b8bb04d0e1d993a08b49ce8d5265e029a21ad6d57b44d4f93243aff4c
|
||||
|
||||
@@ -1 +1 @@
|
||||
ghcr.io/cozystack/cozystack/s3manager:v0.5.0@sha256:79463ccddbbd2a4048863944a858b2aced3f6e1c2d77f7ef4fa57e30624f6e35
|
||||
ghcr.io/cozystack/cozystack/s3manager:v0.5.0@sha256:5bc30fcdc14b289c7eeca3c53388270e3a56d10a3e611fe6c9099afa02661cf4
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
cozystackAPI:
|
||||
image: ghcr.io/cozystack/cozystack/cozystack-api:v0.40.3@sha256:1b5109bc4149178084941cf1cb505110c581c9326dbb1ef94ffdcdb026dff0e8
|
||||
image: ghcr.io/cozystack/cozystack/cozystack-api:v0.40.7@sha256:ac3598860e3a41b466d240c54b5eafceccfa2be5aa084d872e06f5ce7e4054e3
|
||||
localK8sAPIEndpoint:
|
||||
enabled: true
|
||||
replicas: 2
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
cozystackController:
|
||||
image: ghcr.io/cozystack/cozystack/cozystack-controller:v0.40.3@sha256:2b4ad67116c03cd001c495001f9032e6d944199f14a2d80a24d697e2293650c3
|
||||
image: ghcr.io/cozystack/cozystack/cozystack-controller:v0.40.7@sha256:0f746b1fa9be8743c249629cff9f457d63ca4a48875161d2ab598f7e69aa0457
|
||||
debug: false
|
||||
disableTelemetry: false
|
||||
cozystackVersion: "v0.40.3"
|
||||
cozystackVersion: "v0.40.7"
|
||||
cozystackAPIKind: "DaemonSet"
|
||||
|
||||
@@ -3,6 +3,21 @@ module token-proxy
|
||||
go 1.24.0
|
||||
|
||||
require (
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0
|
||||
github.com/gorilla/securecookie v1.1.2
|
||||
github.com/lestrrat-go/httprc/v3 v3.0.2
|
||||
github.com/lestrrat-go/jwx/v3 v3.0.13
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect
|
||||
github.com/goccy/go-json v0.10.3 // indirect
|
||||
github.com/lestrrat-go/blackmagic v1.0.4 // indirect
|
||||
github.com/lestrrat-go/dsig v1.0.0 // indirect
|
||||
github.com/lestrrat-go/dsig-secp256k1 v1.0.0 // indirect
|
||||
github.com/lestrrat-go/httpcc v1.0.1 // indirect
|
||||
github.com/lestrrat-go/option/v2 v2.0.0 // indirect
|
||||
github.com/segmentio/asm v1.2.1 // indirect
|
||||
github.com/valyala/fastjson v1.6.7 // indirect
|
||||
golang.org/x/crypto v0.46.0 // indirect
|
||||
golang.org/x/sys v0.39.0 // indirect
|
||||
)
|
||||
|
||||
@@ -1,6 +1,43 @@
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc=
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40=
|
||||
github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
|
||||
github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
|
||||
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
|
||||
github.com/lestrrat-go/blackmagic v1.0.4 h1:IwQibdnf8l2KoO+qC3uT4OaTWsW7tuRQXy9TRN9QanA=
|
||||
github.com/lestrrat-go/blackmagic v1.0.4/go.mod h1:6AWFyKNNj0zEXQYfTMPfZrAXUWUfTIZ5ECEUEJaijtw=
|
||||
github.com/lestrrat-go/dsig v1.0.0 h1:OE09s2r9Z81kxzJYRn07TFM9XA4akrUdoMwr0L8xj38=
|
||||
github.com/lestrrat-go/dsig v1.0.0/go.mod h1:dEgoOYYEJvW6XGbLasr8TFcAxoWrKlbQvmJgCR0qkDo=
|
||||
github.com/lestrrat-go/dsig-secp256k1 v1.0.0 h1:JpDe4Aybfl0soBvoVwjqDbp+9S1Y2OM7gcrVVMFPOzY=
|
||||
github.com/lestrrat-go/dsig-secp256k1 v1.0.0/go.mod h1:CxUgAhssb8FToqbL8NjSPoGQlnO4w3LG1P0qPWQm/NU=
|
||||
github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE=
|
||||
github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E=
|
||||
github.com/lestrrat-go/httprc/v3 v3.0.2 h1:7u4HUaD0NQbf2/n5+fyp+T10hNCsAnwKfqn4A4Baif0=
|
||||
github.com/lestrrat-go/httprc/v3 v3.0.2/go.mod h1:mSMtkZW92Z98M5YoNNztbRGxbXHql7tSitCvaxvo9l0=
|
||||
github.com/lestrrat-go/jwx/v3 v3.0.13 h1:AdHKiPIYeCSnOJtvdpipPg/0SuFh9rdkN+HF3O0VdSk=
|
||||
github.com/lestrrat-go/jwx/v3 v3.0.13/go.mod h1:2m0PV1A9tM4b/jVLMx8rh6rBl7F6WGb3EG2hufN9OQU=
|
||||
github.com/lestrrat-go/option/v2 v2.0.0 h1:XxrcaJESE1fokHy3FpaQ/cXW8ZsIdWcdFzzLOcID3Ss=
|
||||
github.com/lestrrat-go/option/v2 v2.0.0/go.mod h1:oSySsmzMoR0iRzCDCaUfsCzxQHUEuhOViQObyy7S6Vg=
|
||||
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/segmentio/asm v1.2.1 h1:DTNbBqs57ioxAD4PrArqftgypG4/qNpXoJx8TVXxPR0=
|
||||
github.com/segmentio/asm v1.2.1/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/valyala/fastjson v1.6.7 h1:ZE4tRy0CIkh+qDc5McjatheGX2czdn8slQjomexVpBM=
|
||||
github.com/valyala/fastjson v1.6.7/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY=
|
||||
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
|
||||
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
|
||||
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
|
||||
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
@@ -13,10 +16,13 @@ import (
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"github.com/gorilla/securecookie"
|
||||
"github.com/lestrrat-go/httprc/v3"
|
||||
"github.com/lestrrat-go/jwx/v3/jwk"
|
||||
"github.com/lestrrat-go/jwx/v3/jwt"
|
||||
)
|
||||
|
||||
/* ----------------------------- flags ------------------------------------ */
|
||||
@@ -26,7 +32,9 @@ var (
|
||||
cookieName, cookieSecretB64 string
|
||||
cookieSecure bool
|
||||
cookieRefresh time.Duration
|
||||
tokenCheckURL string
|
||||
jwksURL string
|
||||
saTokenPath string
|
||||
saCACertPath string
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -38,7 +46,70 @@ func init() {
|
||||
flag.StringVar(&cookieSecretB64, "cookie-secret", "", "Base64-encoded cookie secret")
|
||||
flag.BoolVar(&cookieSecure, "cookie-secure", false, "Set Secure flag on cookie")
|
||||
flag.DurationVar(&cookieRefresh, "cookie-refresh", 0, "Cookie refresh interval (e.g. 1h)")
|
||||
flag.StringVar(&tokenCheckURL, "token-check-url", "", "URL for external token validation")
|
||||
flag.StringVar(&jwksURL, "jwks-url", "https://kubernetes.default.svc/openid/v1/jwks", "JWKS URL for token verification")
|
||||
flag.StringVar(&saTokenPath, "sa-token-path", "/var/run/secrets/kubernetes.io/serviceaccount/token", "Path to service account token")
|
||||
flag.StringVar(&saCACertPath, "sa-ca-cert-path", "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt", "Path to service account CA certificate")
|
||||
|
||||
flag.Parse()
|
||||
|
||||
// Initialize jwkCache
|
||||
ctx := context.Background()
|
||||
// Load CA certificate
|
||||
caCert, err := os.ReadFile(saCACertPath)
|
||||
if err != nil {
|
||||
jwkCacheErr := fmt.Errorf("failed to read CA cert: %w", err)
|
||||
panic(jwkCacheErr)
|
||||
}
|
||||
caCertPool := x509.NewCertPool()
|
||||
if !caCertPool.AppendCertsFromPEM(caCert) {
|
||||
jwkCacheErr := fmt.Errorf("failed to parse CA cert")
|
||||
panic(jwkCacheErr)
|
||||
}
|
||||
|
||||
// Create transport with SA token injection
|
||||
transport := &saTokenTransport{
|
||||
base: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{
|
||||
RootCAs: caCertPool,
|
||||
},
|
||||
},
|
||||
tokenPath: saTokenPath,
|
||||
}
|
||||
transport.startRefresh(ctx, 5*time.Minute)
|
||||
|
||||
httpClient := &http.Client{
|
||||
Transport: transport,
|
||||
Timeout: 10 * time.Second,
|
||||
}
|
||||
|
||||
// Create httprc client with custom HTTP client
|
||||
httprcClient := httprc.NewClient(
|
||||
httprc.WithHTTPClient(httpClient),
|
||||
)
|
||||
|
||||
// Create JWK cache
|
||||
jwkCache, err = jwk.NewCache(ctx, httprcClient)
|
||||
if err != nil {
|
||||
jwkCacheErr := fmt.Errorf("failed to create JWK cache: %w", err)
|
||||
panic(jwkCacheErr)
|
||||
}
|
||||
|
||||
// Register the JWKS URL with refresh settings
|
||||
if err := jwkCache.Register(ctx, jwksURL,
|
||||
jwk.WithMinInterval(5*time.Minute),
|
||||
jwk.WithMaxInterval(15*time.Minute),
|
||||
); err != nil {
|
||||
jwkCacheErr := fmt.Errorf("failed to register JWKS URL: %w", err)
|
||||
panic(jwkCacheErr)
|
||||
}
|
||||
|
||||
// Perform initial fetch to ensure the JWKS is available
|
||||
if _, err := jwkCache.Refresh(ctx, jwksURL); err != nil {
|
||||
jwkCacheErr := fmt.Errorf("failed to fetch initial JWKS: %w", err)
|
||||
panic(jwkCacheErr)
|
||||
}
|
||||
|
||||
log.Printf("JWK cache initialized with JWKS URL: %s", jwksURL)
|
||||
}
|
||||
|
||||
/* ----------------------------- templates -------------------------------- */
|
||||
@@ -117,42 +188,94 @@ var loginTmpl = template.Must(template.New("login").Parse(`
|
||||
</body>
|
||||
</html>`))
|
||||
|
||||
/* ----------------------------- helpers ---------------------------------- */
|
||||
/* ----------------------------- JWK cache -------------------------------- */
|
||||
|
||||
func decodeJWT(raw string) jwt.MapClaims {
|
||||
if raw == "" {
|
||||
return jwt.MapClaims{}
|
||||
}
|
||||
tkn, _, err := new(jwt.Parser).ParseUnverified(raw, jwt.MapClaims{})
|
||||
if err != nil || tkn == nil {
|
||||
return jwt.MapClaims{}
|
||||
}
|
||||
if c, ok := tkn.Claims.(jwt.MapClaims); ok {
|
||||
return c
|
||||
}
|
||||
return jwt.MapClaims{}
|
||||
var (
|
||||
jwkCache *jwk.Cache
|
||||
)
|
||||
|
||||
// saTokenTransport adds the service account token to requests and refreshes it periodically.
|
||||
type saTokenTransport struct {
|
||||
base http.RoundTripper
|
||||
tokenPath string
|
||||
mu sync.RWMutex
|
||||
token string
|
||||
}
|
||||
|
||||
func externalTokenCheck(raw string) error {
|
||||
if tokenCheckURL == "" {
|
||||
func (t *saTokenTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
t.mu.RLock()
|
||||
token := t.token
|
||||
t.mu.RUnlock()
|
||||
|
||||
if token != "" {
|
||||
req = req.Clone(req.Context())
|
||||
req.Header.Set("Authorization", "Bearer "+token)
|
||||
}
|
||||
return t.base.RoundTrip(req)
|
||||
}
|
||||
|
||||
func (t *saTokenTransport) refreshToken() {
|
||||
data, err := os.ReadFile(t.tokenPath)
|
||||
if err != nil {
|
||||
log.Printf("warning: failed to read SA token: %v", err)
|
||||
return
|
||||
}
|
||||
t.mu.Lock()
|
||||
t.token = string(data)
|
||||
t.mu.Unlock()
|
||||
}
|
||||
|
||||
func (t *saTokenTransport) startRefresh(ctx context.Context, interval time.Duration) {
|
||||
t.refreshToken()
|
||||
go func() {
|
||||
ticker := time.NewTicker(interval)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-ticker.C:
|
||||
t.refreshToken()
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
/* ----------------------------- helpers ---------------------------------- */
|
||||
|
||||
// verifyAndParseJWT verifies the token signature and returns the parsed token.
|
||||
func verifyAndParseJWT(ctx context.Context, raw string) (jwt.Token, error) {
|
||||
if raw == "" {
|
||||
return nil, fmt.Errorf("empty token")
|
||||
}
|
||||
|
||||
keySet, err := jwkCache.Lookup(ctx, jwksURL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get JWKS: %w", err)
|
||||
}
|
||||
|
||||
token, err := jwt.Parse([]byte(raw), jwt.WithKeySet(keySet))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to verify token: %w", err)
|
||||
}
|
||||
|
||||
return token, nil
|
||||
}
|
||||
|
||||
// getClaim extracts a claim value from a verified token.
|
||||
func getClaim(token jwt.Token, key string) any {
|
||||
if token == nil {
|
||||
return nil
|
||||
}
|
||||
req, _ := http.NewRequest(http.MethodGet, tokenCheckURL, nil)
|
||||
req.Header.Set("Authorization", "Bearer "+raw)
|
||||
cli := &http.Client{Timeout: 5 * time.Second}
|
||||
resp, err := cli.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
var val any
|
||||
if err := token.Get(key, &val); err != nil {
|
||||
return nil
|
||||
}
|
||||
resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("status %d", resp.StatusCode)
|
||||
}
|
||||
return nil
|
||||
return val
|
||||
}
|
||||
|
||||
func encodeSession(sc *securecookie.SecureCookie, token string, exp, issued int64) (string, error) {
|
||||
v := map[string]interface{}{
|
||||
v := map[string]any{
|
||||
"access_token": token,
|
||||
"expires": exp,
|
||||
"issued": issued,
|
||||
@@ -166,7 +289,6 @@ func encodeSession(sc *securecookie.SecureCookie, token string, exp, issued int6
|
||||
/* ----------------------------- main ------------------------------------- */
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
if upstream == "" {
|
||||
log.Fatal("--upstream is required")
|
||||
}
|
||||
@@ -214,7 +336,11 @@ func main() {
|
||||
}{Action: signIn, Err: "Token required"})
|
||||
return
|
||||
}
|
||||
if err := externalTokenCheck(token); err != nil {
|
||||
|
||||
// Verify token signature using JWKS
|
||||
verifiedToken, err := verifyAndParseJWT(r.Context(), token)
|
||||
if err != nil {
|
||||
log.Printf("token verification failed: %v", err)
|
||||
_ = loginTmpl.Execute(w, struct {
|
||||
Action string
|
||||
Err string
|
||||
@@ -223,9 +349,8 @@ func main() {
|
||||
}
|
||||
|
||||
exp := time.Now().Add(24 * time.Hour).Unix()
|
||||
claims := decodeJWT(token)
|
||||
if v, ok := claims["exp"].(float64); ok {
|
||||
exp = int64(v)
|
||||
if expTime, ok := verifiedToken.Expiration(); ok && !expTime.IsZero() {
|
||||
exp = expTime.Unix()
|
||||
}
|
||||
session, _ := encodeSession(sc, token, exp, time.Now().Unix())
|
||||
http.SetCookie(w, &http.Cookie{
|
||||
@@ -264,7 +389,7 @@ func main() {
|
||||
return
|
||||
}
|
||||
var token string
|
||||
var sess map[string]interface{}
|
||||
var sess map[string]any
|
||||
if sc != nil {
|
||||
if err := sc.Decode(cookieName, c.Value, &sess); err != nil {
|
||||
http.Error(w, "unauthorized", http.StatusUnauthorized)
|
||||
@@ -273,19 +398,25 @@ func main() {
|
||||
token, _ = sess["access_token"].(string)
|
||||
} else {
|
||||
token = c.Value
|
||||
sess = map[string]interface{}{
|
||||
sess = map[string]any{
|
||||
"expires": time.Now().Add(24 * time.Hour).Unix(),
|
||||
"issued": time.Now().Unix(),
|
||||
}
|
||||
}
|
||||
claims := decodeJWT(token)
|
||||
|
||||
out := map[string]interface{}{
|
||||
// Re-verify the token to ensure it's still valid
|
||||
verifiedToken, err := verifyAndParseJWT(r.Context(), token)
|
||||
if err != nil {
|
||||
http.Error(w, "unauthorized", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
out := map[string]any{
|
||||
"token": token,
|
||||
"sub": claims["sub"],
|
||||
"email": claims["email"],
|
||||
"preferred_username": claims["preferred_username"],
|
||||
"groups": claims["groups"],
|
||||
"sub": getClaim(verifiedToken, "sub"),
|
||||
"email": getClaim(verifiedToken, "email"),
|
||||
"preferred_username": getClaim(verifiedToken, "preferred_username"),
|
||||
"groups": getClaim(verifiedToken, "groups"),
|
||||
"expires": sess["expires"],
|
||||
"issued": sess["issued"],
|
||||
"cookie_refresh_enable": cookieRefresh > 0,
|
||||
@@ -303,7 +434,7 @@ func main() {
|
||||
return
|
||||
}
|
||||
var token string
|
||||
var sess map[string]interface{}
|
||||
var sess map[string]any
|
||||
if sc != nil {
|
||||
if err := sc.Decode(cookieName, c.Value, &sess); err != nil {
|
||||
http.Redirect(w, r, signIn, http.StatusFound)
|
||||
@@ -312,7 +443,7 @@ func main() {
|
||||
token, _ = sess["access_token"].(string)
|
||||
} else {
|
||||
token = c.Value
|
||||
sess = map[string]interface{}{
|
||||
sess = map[string]any{
|
||||
"expires": time.Now().Add(24 * time.Hour).Unix(),
|
||||
"issued": time.Now().Unix(),
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{{- $brandingConfig := .Values._cluster.branding | default dict }}
|
||||
|
||||
{{- $tenantText := "v0.40.3" }}
|
||||
{{- $tenantText := "v0.40.7" }}
|
||||
{{- $footerText := "Cozystack" }}
|
||||
{{- $titleText := "Cozystack Dashboard" }}
|
||||
{{- $logoText := "" }}
|
||||
|
||||
@@ -2,3 +2,30 @@ apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: incloud-web-gatekeeper
|
||||
{{- $oidcEnabled := index .Values._cluster "oidc-enabled" }}
|
||||
{{- if ne $oidcEnabled "true" }}
|
||||
---
|
||||
# ClusterRole to allow token-proxy to fetch JWKS for JWT verification
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: incloud-web-gatekeeper-jwks
|
||||
rules:
|
||||
- nonResourceURLs:
|
||||
- /openid/v1/jwks
|
||||
verbs:
|
||||
- get
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: incloud-web-gatekeeper-jwks
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: ClusterRole
|
||||
name: incloud-web-gatekeeper-jwks
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: incloud-web-gatekeeper
|
||||
namespace: {{ .Release.Namespace }}
|
||||
{{- end }}
|
||||
|
||||
@@ -64,6 +64,7 @@ spec:
|
||||
- --cookie-secure=true
|
||||
- --cookie-secret=$(OAUTH2_PROXY_COOKIE_SECRET)
|
||||
- --skip-provider-button
|
||||
- --scope=openid email profile offline_access
|
||||
env:
|
||||
- name: OAUTH2_PROXY_CLIENT_ID
|
||||
value: dashboard
|
||||
@@ -88,7 +89,6 @@ spec:
|
||||
- --cookie-name=kc-access
|
||||
- --cookie-secure=true
|
||||
- --cookie-secret=$(TOKEN_PROXY_COOKIE_SECRET)
|
||||
- --token-check-url=http://incloud-web-nginx.{{ .Release.Namespace }}.svc:8080/api/clusters/default/k8s/apis/core.cozystack.io/v1alpha1/tenantnamespaces
|
||||
env:
|
||||
- name: TOKEN_PROXY_COOKIE_SECRET
|
||||
valueFrom:
|
||||
|
||||
@@ -66,6 +66,12 @@ spec:
|
||||
defaultClientScopes:
|
||||
- groups
|
||||
- kubernetes-client
|
||||
optionalClientScopes:
|
||||
- offline_access
|
||||
attributes:
|
||||
post.logout.redirect.uris: "+"
|
||||
client.session.idle.timeout: "86400"
|
||||
client.session.max.lifespan: "604800"
|
||||
redirectUris:
|
||||
- "https://dashboard.{{ $host }}/oauth2/callback/*"
|
||||
{{- range $i, $v := $extraRedirectUris }}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
openapiUI:
|
||||
image: ghcr.io/cozystack/cozystack/openapi-ui:v0.40.3@sha256:9666e61fdf7d4ddfeb9084f49ee9c1297b67a4159c475fe62680a0d84ea4050c
|
||||
image: ghcr.io/cozystack/cozystack/openapi-ui:v0.40.7@sha256:7ec24b3ecf1e72a6203f337101fb8b326bd319c540bea2f34c5458b836d46867
|
||||
openapiUIK8sBff:
|
||||
image: ghcr.io/cozystack/cozystack/openapi-ui-k8s-bff:v0.40.3@sha256:c905f98598526820bd71e3997fdc62868b9c3d7a4e9700b01d73b017b0953257
|
||||
image: ghcr.io/cozystack/cozystack/openapi-ui-k8s-bff:v0.40.7@sha256:d33583995dc81a47c1dcbe45dbd866fa9097f88f4b6eb78b408dca432f15bd38
|
||||
tokenProxy:
|
||||
image: ghcr.io/cozystack/cozystack/token-proxy:v0.40.3@sha256:73887f80d96e7e3c16f1cebab521b05b4308bf4662ccc6724e6a8a9745ed8254
|
||||
image: ghcr.io/cozystack/cozystack/token-proxy:v0.40.7@sha256:2e280991e07853ea48f97b0a42946afffa10d03d6a83d41099ed83e6ffc94fdc
|
||||
|
||||
@@ -3,7 +3,7 @@ kamaji:
|
||||
deploy: false
|
||||
image:
|
||||
pullPolicy: IfNotPresent
|
||||
tag: v0.40.3@sha256:777f1df253dc97da7f42019857e7a893c34bd98da1cf36e38533adbdf601acf7
|
||||
tag: v0.40.7@sha256:fe9b6bb548edfc26be8aaac65801d598a4e2f9884ddf748083b9e509fa00259e
|
||||
repository: ghcr.io/cozystack/cozystack/kamaji
|
||||
resources:
|
||||
limits:
|
||||
@@ -13,4 +13,4 @@ kamaji:
|
||||
cpu: 100m
|
||||
memory: 100Mi
|
||||
extraArgs:
|
||||
- --migrate-image=ghcr.io/cozystack/cozystack/kamaji:v0.40.3@sha256:777f1df253dc97da7f42019857e7a893c34bd98da1cf36e38533adbdf601acf7
|
||||
- --migrate-image=ghcr.io/cozystack/cozystack/kamaji:v0.40.7@sha256:fe9b6bb548edfc26be8aaac65801d598a4e2f9884ddf748083b9e509fa00259e
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
portSecurity: true
|
||||
routes: ""
|
||||
image: ghcr.io/cozystack/cozystack/kubeovn-plunger:v0.40.3@sha256:864a75eb6fb07eedef3b9ff94e6bb6b02e94fe73c1e03bf360bc785fbfbc1170
|
||||
image: ghcr.io/cozystack/cozystack/kubeovn-plunger:v0.40.7@sha256:34e603d6d527ad07d0fd6f969ff4b4f2a98abfdbfacc020ccd9eb61b8394b1c1
|
||||
ovnCentralName: ovn-central
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
portSecurity: true
|
||||
routes: ""
|
||||
image: ghcr.io/cozystack/cozystack/kubeovn-webhook:v0.40.3@sha256:e18f9fd679e38f65362a8d0042f25468272f6d081136ad47027168d8e7e07a4a
|
||||
image: ghcr.io/cozystack/cozystack/kubeovn-webhook:v0.40.7@sha256:e6334c29d3aaf0dea766c88e3e05b53ad623d1bb497b3c836e6f76adade45b29
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
storageClass: replicated
|
||||
csiDriver:
|
||||
image: ghcr.io/cozystack/cozystack/kubevirt-csi-driver:0.0.0@sha256:15b94dca216b73336e7f39f4ea1b76b7656890d6be8a8cf0d9a786b4006781f9
|
||||
image: ghcr.io/cozystack/cozystack/kubevirt-csi-driver:0.0.0@sha256:0b208ed506dd8c453426761d93ec3d42c9d1b791ba6c91b01c6386dcb1b02442
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
lineageControllerWebhook:
|
||||
image: ghcr.io/cozystack/cozystack/lineage-controller-webhook:v0.40.3@sha256:3556a8ca2ee280dff3991519a5f536ec6ea0a4e5f9be0572f23adb8f59466e56
|
||||
image: ghcr.io/cozystack/cozystack/lineage-controller-webhook:v0.40.7@sha256:1680ba3634c81691c2cf346759c7746cf700ce59fd752fce862e5e89f3a6fdb5
|
||||
debug: false
|
||||
localK8sAPIEndpoint:
|
||||
enabled: true
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
piraeusServer:
|
||||
image:
|
||||
repository: ghcr.io/cozystack/cozystack/piraeus-server
|
||||
tag: 1.32.3@sha256:1138c8dc0a117360ef70e2e2ab97bc2696419b63f46358f7668c7e01a96c419b
|
||||
tag: 1.32.3@sha256:3d1b4348c665fb88f8bead09a1fa68547e6872172ed0168449cb232c4467ad84
|
||||
linstor:
|
||||
autoDiskful:
|
||||
enabled: true
|
||||
@@ -10,4 +10,4 @@ linstor:
|
||||
linstorCSI:
|
||||
image:
|
||||
repository: ghcr.io/cozystack/cozystack/linstor-csi
|
||||
tag: v1.10.5@sha256:68465f120cfeec3d7ccbb389dd9bdbf7df1675da3ab9ba91c3feff21a799bc36
|
||||
tag: v1.10.5@sha256:6e6cf48cb994f3918df946e02ec454ac64916678b3e60d78c136b431f1a26155
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
objectstorage:
|
||||
controller:
|
||||
image: "ghcr.io/cozystack/cozystack/objectstorage-controller:v0.40.3@sha256:76756860145d49abe1585c8ca600267a07fcdbb0b359bc9c127baf9efb8e006b"
|
||||
image: "ghcr.io/cozystack/cozystack/objectstorage-controller:v0.40.7@sha256:85c9dd60b4a5e2a9ce7e5675f6a62bb4d3bb95656c7bcf6b64c87e9a56aadf9d"
|
||||
|
||||
@@ -177,7 +177,7 @@ seaweedfs:
|
||||
bucketClassName: "seaweedfs"
|
||||
region: ""
|
||||
sidecar:
|
||||
image: "ghcr.io/cozystack/cozystack/objectstorage-sidecar:v0.40.3@sha256:d342ac197b97b81ec42712ecd9d762c6c7710a401736e9d09c64a5b8d59774cc"
|
||||
image: "ghcr.io/cozystack/cozystack/objectstorage-sidecar:v0.40.7@sha256:3e61975b8bb04d0e1d993a08b49ce8d5265e029a21ad6d57b44d4f93243aff4c"
|
||||
certificates:
|
||||
commonName: "SeaweedFS CA"
|
||||
ipAddresses: []
|
||||
|
||||
Reference in New Issue
Block a user