mirror of
https://github.com/zitadel/zitadel.git
synced 2025-05-05 16:30:46 +00:00
feat: system api requires authenticated requests (#3570)
* begin auth * feat: system api requires authenticated requests * fix tests
This commit is contained in:
parent
41d78ef523
commit
2fc39c0da0
@ -16,14 +16,6 @@ RUN go mod download
|
|||||||
COPY tools ./tools
|
COPY tools ./tools
|
||||||
RUN ./tools/install.sh
|
RUN ./tools/install.sh
|
||||||
|
|
||||||
ARG COCKROACH_BINARY=cockroach
|
|
||||||
RUN apt install openssl tzdata tar
|
|
||||||
|
|
||||||
# cockroach binary used to backup database
|
|
||||||
RUN mkdir /usr/local/lib/cockroach
|
|
||||||
RUN wget -qO- https://binaries.cockroachdb.com/cockroach-v21.2.9.linux-amd64.tgz \
|
|
||||||
| tar xvz && cp -i cockroach-v21.2.9.linux-amd64/cockroach /usr/local/bin/
|
|
||||||
RUN rm -r cockroach-v21.2.9.linux-amd64
|
|
||||||
|
|
||||||
#######################
|
#######################
|
||||||
## generates static files
|
## generates static files
|
||||||
@ -92,6 +84,15 @@ COPY --from=go-stub /go/src/github.com/zitadel/zitadel/internal/api/assets/route
|
|||||||
#######################
|
#######################
|
||||||
FROM go-base as go-test
|
FROM go-base as go-test
|
||||||
|
|
||||||
|
ARG COCKROACH_BINARY=cockroach
|
||||||
|
RUN apt install openssl tzdata tar
|
||||||
|
|
||||||
|
# cockroach binary used to backup database
|
||||||
|
RUN mkdir /usr/local/lib/cockroach
|
||||||
|
RUN wget -qO- https://binaries.cockroachdb.com/cockroach-v21.2.9.linux-amd64.tgz \
|
||||||
|
| tar xvz && cp -i cockroach-v21.2.9.linux-amd64/cockroach /usr/local/bin/
|
||||||
|
RUN rm -r cockroach-v21.2.9.linux-amd64
|
||||||
|
|
||||||
# Migrations for cockroach-secure
|
# Migrations for cockroach-secure
|
||||||
RUN go install github.com/rakyll/statik \
|
RUN go install github.com/rakyll/statik \
|
||||||
&& go test -race -v -coverprofile=profile.cov $(go list ./... | grep -v /operator/)
|
&& go test -race -v -coverprofile=profile.cov $(go list ./... | grep -v /operator/)
|
||||||
|
@ -50,6 +50,7 @@ type Config struct {
|
|||||||
EncryptionKeys *encryptionKeyConfig
|
EncryptionKeys *encryptionKeyConfig
|
||||||
DefaultInstance command.InstanceSetup
|
DefaultInstance command.InstanceSetup
|
||||||
AuditLogRetention time.Duration
|
AuditLogRetention time.Duration
|
||||||
|
SystemAPIUsers map[string]*internal_authz.SystemAPIUser
|
||||||
}
|
}
|
||||||
|
|
||||||
func MustNewConfig(v *viper.Viper) *Config {
|
func MustNewConfig(v *viper.Viper) *Config {
|
||||||
|
@ -135,14 +135,14 @@ func startZitadel(config *Config, masterKey string) error {
|
|||||||
notification.Start(config.Notification, config.ExternalPort, config.ExternalSecure, commands, queries, dbClient, assets.HandlerPrefix, config.SystemDefaults.Notifications.FileSystemPath, keys.User, keys.SMTP, keys.SMS)
|
notification.Start(config.Notification, config.ExternalPort, config.ExternalSecure, commands, queries, dbClient, assets.HandlerPrefix, config.SystemDefaults.Notifications.FileSystemPath, keys.User, keys.SMTP, keys.SMS)
|
||||||
|
|
||||||
router := mux.NewRouter()
|
router := mux.NewRouter()
|
||||||
err = startAPIs(ctx, router, commands, queries, eventstoreClient, dbClient, config, storage, authZRepo, keys)
|
err = startAPIs(ctx, router, commands, queries, eventstoreClient, dbClient, config, storage, authZRepo, keys, config.SystemAPIUsers)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return listen(ctx, router, config.Port)
|
return listen(ctx, router, config.Port)
|
||||||
}
|
}
|
||||||
|
|
||||||
func startAPIs(ctx context.Context, router *mux.Router, commands *command.Commands, queries *query.Queries, eventstore *eventstore.Eventstore, dbClient *sql.DB, config *Config, store static.Storage, authZRepo authz_repo.Repository, keys *encryptionKeys) error {
|
func startAPIs(ctx context.Context, router *mux.Router, commands *command.Commands, queries *query.Queries, eventstore *eventstore.Eventstore, dbClient *sql.DB, config *Config, store static.Storage, authZRepo authz_repo.Repository, keys *encryptionKeys, systemAPIKeys map[string]*internal_authz.SystemAPIUser) error {
|
||||||
repo := struct {
|
repo := struct {
|
||||||
authz_repo.Repository
|
authz_repo.Repository
|
||||||
*query.Queries
|
*query.Queries
|
||||||
@ -150,9 +150,9 @@ func startAPIs(ctx context.Context, router *mux.Router, commands *command.Comman
|
|||||||
authZRepo,
|
authZRepo,
|
||||||
queries,
|
queries,
|
||||||
}
|
}
|
||||||
verifier := internal_authz.Start(repo)
|
verifier := internal_authz.Start(repo, config.ExternalDomain, systemAPIKeys)
|
||||||
|
|
||||||
authenticatedAPIs := api.New(config.Port, router, &repo, config.InternalAuthZ, config.ExternalSecure, config.HTTP2HostHeader)
|
apis := api.New(config.Port, router, queries, verifier, config.InternalAuthZ, config.ExternalSecure, config.HTTP2HostHeader)
|
||||||
authRepo, err := auth_es.Start(config.Auth, config.SystemDefaults, commands, queries, dbClient, keys.OIDC, keys.User)
|
authRepo, err := auth_es.Start(config.Auth, config.SystemDefaults, commands, queries, dbClient, keys.OIDC, keys.User)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error starting auth repo: %w", err)
|
return fmt.Errorf("error starting auth repo: %w", err)
|
||||||
@ -161,21 +161,21 @@ func startAPIs(ctx context.Context, router *mux.Router, commands *command.Comman
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error starting admin repo: %w", err)
|
return fmt.Errorf("error starting admin repo: %w", err)
|
||||||
}
|
}
|
||||||
if err := authenticatedAPIs.RegisterServer(ctx, system.CreateServer(commands, queries, adminRepo, config.Database.Database, config.DefaultInstance)); err != nil {
|
if err := apis.RegisterServer(ctx, system.CreateServer(commands, queries, adminRepo, config.Database.Database, config.DefaultInstance)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := authenticatedAPIs.RegisterServer(ctx, admin.CreateServer(commands, queries, adminRepo, config.ExternalSecure, keys.User)); err != nil {
|
if err := apis.RegisterServer(ctx, admin.CreateServer(commands, queries, adminRepo, config.ExternalSecure, keys.User)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := authenticatedAPIs.RegisterServer(ctx, management.CreateServer(commands, queries, config.SystemDefaults, keys.User, config.ExternalSecure, oidc.HandlerPrefix, config.AuditLogRetention)); err != nil {
|
if err := apis.RegisterServer(ctx, management.CreateServer(commands, queries, config.SystemDefaults, keys.User, config.ExternalSecure, oidc.HandlerPrefix, config.AuditLogRetention)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := authenticatedAPIs.RegisterServer(ctx, auth.CreateServer(commands, queries, authRepo, config.SystemDefaults, keys.User, config.ExternalSecure, config.AuditLogRetention)); err != nil {
|
if err := apis.RegisterServer(ctx, auth.CreateServer(commands, queries, authRepo, config.SystemDefaults, keys.User, config.ExternalSecure, config.AuditLogRetention)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
instanceInterceptor := middleware.InstanceInterceptor(queries, config.HTTP1HostHeader, login.IgnoreInstanceEndpoints...)
|
instanceInterceptor := middleware.InstanceInterceptor(queries, config.HTTP1HostHeader, login.IgnoreInstanceEndpoints...)
|
||||||
authenticatedAPIs.RegisterHandler(assets.HandlerPrefix, assets.NewHandler(commands, verifier, config.InternalAuthZ, id.SonyFlakeGenerator(), store, queries, instanceInterceptor.Handler))
|
apis.RegisterHandler(assets.HandlerPrefix, assets.NewHandler(commands, verifier, config.InternalAuthZ, id.SonyFlakeGenerator(), store, queries, instanceInterceptor.Handler))
|
||||||
|
|
||||||
userAgentInterceptor, err := middleware.NewUserAgentHandler(config.UserAgentCookie, keys.UserAgentCookieKey, id.SonyFlakeGenerator(), config.ExternalSecure, login.EndpointResources)
|
userAgentInterceptor, err := middleware.NewUserAgentHandler(config.UserAgentCookie, keys.UserAgentCookieKey, id.SonyFlakeGenerator(), config.ExternalSecure, login.EndpointResources)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -186,25 +186,25 @@ func startAPIs(ctx context.Context, router *mux.Router, commands *command.Comman
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("unable to start oidc provider: %w", err)
|
return fmt.Errorf("unable to start oidc provider: %w", err)
|
||||||
}
|
}
|
||||||
authenticatedAPIs.RegisterHandler(oidc.HandlerPrefix, oidcProvider.HttpHandler())
|
apis.RegisterHandler(oidc.HandlerPrefix, oidcProvider.HttpHandler())
|
||||||
|
|
||||||
openAPIHandler, err := openapi.Start()
|
openAPIHandler, err := openapi.Start()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("unable to start openapi handler: %w", err)
|
return fmt.Errorf("unable to start openapi handler: %w", err)
|
||||||
}
|
}
|
||||||
authenticatedAPIs.RegisterHandler(openapi.HandlerPrefix, openAPIHandler)
|
apis.RegisterHandler(openapi.HandlerPrefix, openAPIHandler)
|
||||||
|
|
||||||
c, err := console.Start(config.Console, config.ExternalSecure, oidcProvider.IssuerFromRequest, instanceInterceptor.Handler)
|
c, err := console.Start(config.Console, config.ExternalSecure, oidcProvider.IssuerFromRequest, instanceInterceptor.Handler)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("unable to start console: %w", err)
|
return fmt.Errorf("unable to start console: %w", err)
|
||||||
}
|
}
|
||||||
authenticatedAPIs.RegisterHandler(console.HandlerPrefix, c)
|
apis.RegisterHandler(console.HandlerPrefix, c)
|
||||||
|
|
||||||
l, err := login.CreateLogin(config.Login, commands, queries, authRepo, store, console.HandlerPrefix+"/", op.AuthCallbackURL(oidcProvider), config.ExternalSecure, userAgentInterceptor, op.NewIssuerInterceptor(oidcProvider.IssuerFromRequest).Handler, instanceInterceptor.Handler, keys.User, keys.IDPConfig, keys.CSRFCookieKey)
|
l, err := login.CreateLogin(config.Login, commands, queries, authRepo, store, console.HandlerPrefix+"/", op.AuthCallbackURL(oidcProvider), config.ExternalSecure, userAgentInterceptor, op.NewIssuerInterceptor(oidcProvider.IssuerFromRequest).Handler, instanceInterceptor.Handler, keys.User, keys.IDPConfig, keys.CSRFCookieKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("unable to start login: %w", err)
|
return fmt.Errorf("unable to start login: %w", err)
|
||||||
}
|
}
|
||||||
authenticatedAPIs.RegisterHandler(login.HandlerPrefix, l.Handler())
|
apis.RegisterHandler(login.HandlerPrefix, l.Handler())
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
"github.com/zitadel/logging"
|
"github.com/zitadel/logging"
|
||||||
|
|
||||||
"github.com/zitadel/zitadel/cmd/admin/initialise"
|
"github.com/zitadel/zitadel/cmd/admin/initialise"
|
||||||
"github.com/zitadel/zitadel/cmd/admin/key"
|
"github.com/zitadel/zitadel/cmd/admin/key"
|
||||||
"github.com/zitadel/zitadel/cmd/admin/setup"
|
"github.com/zitadel/zitadel/cmd/admin/setup"
|
||||||
|
@ -163,6 +163,13 @@ EncryptionKeys:
|
|||||||
CSRFCookieKeyID: "csrfCookieKey"
|
CSRFCookieKeyID: "csrfCookieKey"
|
||||||
UserAgentCookieKeyID: "userAgentCookieKey"
|
UserAgentCookieKeyID: "userAgentCookieKey"
|
||||||
|
|
||||||
|
SystemAPIUsers:
|
||||||
|
# add keys for authentication of the systemAPI here:
|
||||||
|
# - superuser:
|
||||||
|
# Path: /path/to/superuser/key.pem
|
||||||
|
# - superuser2:
|
||||||
|
# Path: /path/to/superuser2/key.pem
|
||||||
|
|
||||||
#TODO: remove as soon as possible
|
#TODO: remove as soon as possible
|
||||||
SystemDefaults:
|
SystemDefaults:
|
||||||
SecretGenerators:
|
SecretGenerators:
|
||||||
|
@ -14,7 +14,6 @@ import (
|
|||||||
internal_authz "github.com/zitadel/zitadel/internal/api/authz"
|
internal_authz "github.com/zitadel/zitadel/internal/api/authz"
|
||||||
"github.com/zitadel/zitadel/internal/api/grpc/server"
|
"github.com/zitadel/zitadel/internal/api/grpc/server"
|
||||||
http_util "github.com/zitadel/zitadel/internal/api/http"
|
http_util "github.com/zitadel/zitadel/internal/api/http"
|
||||||
"github.com/zitadel/zitadel/internal/authz/repository"
|
|
||||||
"github.com/zitadel/zitadel/internal/errors"
|
"github.com/zitadel/zitadel/internal/errors"
|
||||||
"github.com/zitadel/zitadel/internal/query"
|
"github.com/zitadel/zitadel/internal/query"
|
||||||
"github.com/zitadel/zitadel/internal/telemetry/tracing"
|
"github.com/zitadel/zitadel/internal/telemetry/tracing"
|
||||||
@ -37,23 +36,20 @@ type health interface {
|
|||||||
func New(
|
func New(
|
||||||
port uint16,
|
port uint16,
|
||||||
router *mux.Router,
|
router *mux.Router,
|
||||||
repo *struct {
|
queries *query.Queries,
|
||||||
repository.Repository
|
verifier *internal_authz.TokenVerifier,
|
||||||
*query.Queries
|
|
||||||
},
|
|
||||||
authZ internal_authz.Config,
|
authZ internal_authz.Config,
|
||||||
externalSecure bool,
|
externalSecure bool,
|
||||||
http2HostName string,
|
http2HostName string,
|
||||||
) *API {
|
) *API {
|
||||||
verifier := internal_authz.Start(repo)
|
|
||||||
api := &API{
|
api := &API{
|
||||||
port: port,
|
port: port,
|
||||||
verifier: verifier,
|
verifier: verifier,
|
||||||
health: repo,
|
health: queries,
|
||||||
router: router,
|
router: router,
|
||||||
externalSecure: externalSecure,
|
externalSecure: externalSecure,
|
||||||
}
|
}
|
||||||
api.grpcServer = server.CreateServer(api.verifier, authZ, repo.Queries, http2HostName)
|
api.grpcServer = server.CreateServer(api.verifier, authZ, queries, http2HostName)
|
||||||
api.routeGRPC()
|
api.routeGRPC()
|
||||||
|
|
||||||
api.RegisterHandler("/debug", api.healthHandler())
|
api.RegisterHandler("/debug", api.healthHandler())
|
||||||
@ -68,9 +64,7 @@ func (a *API) RegisterServer(ctx context.Context, grpcServer server.Server) erro
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
a.RegisterHandler(prefix, handler)
|
a.RegisterHandler(prefix, handler)
|
||||||
if a.verifier != nil {
|
a.verifier.RegisterServer(grpcServer.AppName(), grpcServer.MethodPrefix(), grpcServer.AuthMethods())
|
||||||
a.verifier.RegisterServer(grpcServer.AppName(), grpcServer.MethodPrefix(), grpcServer.AuthMethods())
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@ package authz
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/zitadel/zitadel/internal/api/grpc"
|
"github.com/zitadel/zitadel/internal/api/grpc"
|
||||||
http_util "github.com/zitadel/zitadel/internal/api/http"
|
http_util "github.com/zitadel/zitadel/internal/api/http"
|
||||||
@ -67,6 +68,9 @@ func VerifyTokenAndCreateCtxData(ctx context.Context, token, orgID string, t *To
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return CtxData{}, err
|
return CtxData{}, err
|
||||||
}
|
}
|
||||||
|
if strings.HasPrefix(method, "/zitadel.system.v1.SystemService") {
|
||||||
|
return CtxData{UserID: userID}, nil
|
||||||
|
}
|
||||||
var projectID string
|
var projectID string
|
||||||
var origins []string
|
var origins []string
|
||||||
if clientID != "" {
|
if clientID != "" {
|
||||||
|
@ -68,7 +68,7 @@ func Test_GetUserMethodPermissions(t *testing.T) {
|
|||||||
{
|
{
|
||||||
Roles: []string{"ORG_OWNER"},
|
Roles: []string{"ORG_OWNER"},
|
||||||
},
|
},
|
||||||
}}),
|
}}, "", nil),
|
||||||
requiredPerm: "project.read",
|
requiredPerm: "project.read",
|
||||||
authConfig: Config{
|
authConfig: Config{
|
||||||
RolePermissionMappings: []RoleMapping{
|
RolePermissionMappings: []RoleMapping{
|
||||||
@ -91,7 +91,7 @@ func Test_GetUserMethodPermissions(t *testing.T) {
|
|||||||
name: "No Grants",
|
name: "No Grants",
|
||||||
args: args{
|
args: args{
|
||||||
ctxData: CtxData{},
|
ctxData: CtxData{},
|
||||||
verifier: Start(&testVerifier{memberships: []*Membership{}}),
|
verifier: Start(&testVerifier{memberships: []*Membership{}}, "", nil),
|
||||||
requiredPerm: "project.read",
|
requiredPerm: "project.read",
|
||||||
authConfig: Config{
|
authConfig: Config{
|
||||||
RolePermissionMappings: []RoleMapping{
|
RolePermissionMappings: []RoleMapping{
|
||||||
@ -119,7 +119,7 @@ func Test_GetUserMethodPermissions(t *testing.T) {
|
|||||||
MemberType: MemberTypeIam,
|
MemberType: MemberTypeIam,
|
||||||
Roles: []string{"IAM_OWNER"},
|
Roles: []string{"IAM_OWNER"},
|
||||||
},
|
},
|
||||||
}}),
|
}}, "", nil),
|
||||||
requiredPerm: "project.read",
|
requiredPerm: "project.read",
|
||||||
authConfig: Config{
|
authConfig: Config{
|
||||||
RolePermissionMappings: []RoleMapping{
|
RolePermissionMappings: []RoleMapping{
|
||||||
|
@ -2,9 +2,16 @@ package authz
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/rsa"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/zitadel/oidc/v2/pkg/op"
|
||||||
|
"gopkg.in/square/go-jose.v2"
|
||||||
|
|
||||||
|
"github.com/zitadel/zitadel/internal/crypto"
|
||||||
caos_errs "github.com/zitadel/zitadel/internal/errors"
|
caos_errs "github.com/zitadel/zitadel/internal/errors"
|
||||||
"github.com/zitadel/zitadel/internal/telemetry/tracing"
|
"github.com/zitadel/zitadel/internal/telemetry/tracing"
|
||||||
)
|
)
|
||||||
@ -14,9 +21,10 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type TokenVerifier struct {
|
type TokenVerifier struct {
|
||||||
authZRepo authZRepo
|
authZRepo authZRepo
|
||||||
clients sync.Map
|
clients sync.Map
|
||||||
authMethods MethodMapping
|
authMethods MethodMapping
|
||||||
|
systemJWTProfile op.JWTProfileVerifier
|
||||||
}
|
}
|
||||||
|
|
||||||
type authZRepo interface {
|
type authZRepo interface {
|
||||||
@ -27,15 +35,74 @@ type authZRepo interface {
|
|||||||
ExistsOrg(ctx context.Context, orgID string) error
|
ExistsOrg(ctx context.Context, orgID string) error
|
||||||
}
|
}
|
||||||
|
|
||||||
func Start(authZRepo authZRepo) (v *TokenVerifier) {
|
func Start(authZRepo authZRepo, systemAPI string, keys map[string]*SystemAPIUser) (v *TokenVerifier) {
|
||||||
return &TokenVerifier{authZRepo: authZRepo}
|
return &TokenVerifier{
|
||||||
|
authZRepo: authZRepo,
|
||||||
|
systemJWTProfile: op.NewJWTProfileVerifier(
|
||||||
|
&systemJWTStorage{
|
||||||
|
keys: keys,
|
||||||
|
cachedKeys: make(map[string]*rsa.PublicKey),
|
||||||
|
},
|
||||||
|
systemAPI,
|
||||||
|
1*time.Hour,
|
||||||
|
time.Second,
|
||||||
|
),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *TokenVerifier) VerifyAccessToken(ctx context.Context, token string, method string) (userID, clientID, agentID, prefLang, resourceOwner string, err error) {
|
func (v *TokenVerifier) VerifyAccessToken(ctx context.Context, token string, method string) (userID, clientID, agentID, prefLang, resourceOwner string, err error) {
|
||||||
|
if strings.HasPrefix(method, "/zitadel.system.v1.SystemService") {
|
||||||
|
userID, err := v.verifySystemToken(ctx, token)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", "", "", "", err
|
||||||
|
}
|
||||||
|
return userID, "", "", "", "", nil
|
||||||
|
}
|
||||||
userID, agentID, clientID, prefLang, resourceOwner, err = v.authZRepo.VerifyAccessToken(ctx, token, "", GetInstance(ctx).ProjectID())
|
userID, agentID, clientID, prefLang, resourceOwner, err = v.authZRepo.VerifyAccessToken(ctx, token, "", GetInstance(ctx).ProjectID())
|
||||||
return userID, clientID, agentID, prefLang, resourceOwner, err
|
return userID, clientID, agentID, prefLang, resourceOwner, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (v *TokenVerifier) verifySystemToken(ctx context.Context, token string) (string, error) {
|
||||||
|
jwtReq, err := op.VerifyJWTAssertion(ctx, token, v.systemJWTProfile)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return jwtReq.Subject, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type systemJWTStorage struct {
|
||||||
|
keys map[string]*SystemAPIUser
|
||||||
|
mutex sync.Mutex
|
||||||
|
cachedKeys map[string]*rsa.PublicKey
|
||||||
|
}
|
||||||
|
|
||||||
|
type SystemAPIUser struct {
|
||||||
|
Path string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *systemJWTStorage) GetKeyByIDAndUserID(_ context.Context, _, userID string) (*jose.JSONWebKey, error) {
|
||||||
|
cachedKey, ok := s.cachedKeys[userID]
|
||||||
|
if ok {
|
||||||
|
return &jose.JSONWebKey{KeyID: userID, Key: cachedKey}, nil
|
||||||
|
}
|
||||||
|
key, ok := s.keys[userID]
|
||||||
|
if !ok {
|
||||||
|
return nil, caos_errs.ThrowNotFound(nil, "AUTHZ-asfd3", "Errors.User.NotFound")
|
||||||
|
}
|
||||||
|
defer s.mutex.Unlock()
|
||||||
|
s.mutex.Lock()
|
||||||
|
keyData, err := os.ReadFile(key.Path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, caos_errs.ThrowInternal(err, "AUTHZ-JK31F", "Errors.NotFound")
|
||||||
|
}
|
||||||
|
publicKey, err := crypto.BytesToPublicKey(keyData)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
s.cachedKeys[userID] = publicKey
|
||||||
|
return &jose.JSONWebKey{KeyID: userID, Key: publicKey}, nil
|
||||||
|
}
|
||||||
|
|
||||||
type client struct {
|
type client struct {
|
||||||
id string
|
id string
|
||||||
projectID string
|
projectID string
|
||||||
|
@ -15,10 +15,6 @@ import (
|
|||||||
|
|
||||||
func AuthorizationInterceptor(verifier *authz.TokenVerifier, authConfig authz.Config) grpc.UnaryServerInterceptor {
|
func AuthorizationInterceptor(verifier *authz.TokenVerifier, authConfig authz.Config) grpc.UnaryServerInterceptor {
|
||||||
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
|
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
|
||||||
//TODO: Change as soon as we know how to authenticate system api
|
|
||||||
if verifier == nil {
|
|
||||||
return handler(ctx, req)
|
|
||||||
}
|
|
||||||
return authorize(ctx, req, info, handler, verifier, authConfig)
|
return authorize(ctx, req, info, handler, verifier, authConfig)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -65,7 +65,7 @@ func Test_authorize(t *testing.T) {
|
|||||||
info: mockInfo("/no/token/needed"),
|
info: mockInfo("/no/token/needed"),
|
||||||
handler: emptyMockHandler,
|
handler: emptyMockHandler,
|
||||||
verifier: func() *authz.TokenVerifier {
|
verifier: func() *authz.TokenVerifier {
|
||||||
verifier := authz.Start(&verifierMock{})
|
verifier := authz.Start(&verifierMock{}, "", nil)
|
||||||
verifier.RegisterServer("need", "need", authz.MethodMapping{})
|
verifier.RegisterServer("need", "need", authz.MethodMapping{})
|
||||||
return verifier
|
return verifier
|
||||||
}(),
|
}(),
|
||||||
@ -84,7 +84,7 @@ func Test_authorize(t *testing.T) {
|
|||||||
info: mockInfo("/need/authentication"),
|
info: mockInfo("/need/authentication"),
|
||||||
handler: emptyMockHandler,
|
handler: emptyMockHandler,
|
||||||
verifier: func() *authz.TokenVerifier {
|
verifier: func() *authz.TokenVerifier {
|
||||||
verifier := authz.Start(&verifierMock{})
|
verifier := authz.Start(&verifierMock{}, "", nil)
|
||||||
verifier.RegisterServer("need", "need", authz.MethodMapping{"/need/authentication": authz.Option{Permission: "authenticated"}})
|
verifier.RegisterServer("need", "need", authz.MethodMapping{"/need/authentication": authz.Option{Permission: "authenticated"}})
|
||||||
return verifier
|
return verifier
|
||||||
}(),
|
}(),
|
||||||
@ -104,7 +104,7 @@ func Test_authorize(t *testing.T) {
|
|||||||
info: mockInfo("/need/authentication"),
|
info: mockInfo("/need/authentication"),
|
||||||
handler: emptyMockHandler,
|
handler: emptyMockHandler,
|
||||||
verifier: func() *authz.TokenVerifier {
|
verifier: func() *authz.TokenVerifier {
|
||||||
verifier := authz.Start(&verifierMock{})
|
verifier := authz.Start(&verifierMock{}, "", nil)
|
||||||
verifier.RegisterServer("need", "need", authz.MethodMapping{"/need/authentication": authz.Option{Permission: "authenticated"}})
|
verifier.RegisterServer("need", "need", authz.MethodMapping{"/need/authentication": authz.Option{Permission: "authenticated"}})
|
||||||
return verifier
|
return verifier
|
||||||
}(),
|
}(),
|
||||||
@ -124,7 +124,7 @@ func Test_authorize(t *testing.T) {
|
|||||||
info: mockInfo("/need/authentication"),
|
info: mockInfo("/need/authentication"),
|
||||||
handler: emptyMockHandler,
|
handler: emptyMockHandler,
|
||||||
verifier: func() *authz.TokenVerifier {
|
verifier: func() *authz.TokenVerifier {
|
||||||
verifier := authz.Start(&verifierMock{})
|
verifier := authz.Start(&verifierMock{}, "", nil)
|
||||||
verifier.RegisterServer("need", "need", authz.MethodMapping{"/need/authentication": authz.Option{Permission: "authenticated"}})
|
verifier.RegisterServer("need", "need", authz.MethodMapping{"/need/authentication": authz.Option{Permission: "authenticated"}})
|
||||||
return verifier
|
return verifier
|
||||||
}(),
|
}(),
|
||||||
|
@ -28,6 +28,9 @@ func setInstance(ctx context.Context, req interface{}, info *grpc.UnaryServerInf
|
|||||||
interceptorCtx, span := tracing.NewServerInterceptorSpan(ctx)
|
interceptorCtx, span := tracing.NewServerInterceptorSpan(ctx)
|
||||||
defer func() { span.EndWithError(err) }()
|
defer func() { span.EndWithError(err) }()
|
||||||
for _, service := range ignoredServices {
|
for _, service := range ignoredServices {
|
||||||
|
if !strings.HasPrefix(service, "/") {
|
||||||
|
service = "/" + service
|
||||||
|
}
|
||||||
if strings.HasPrefix(info.FullMethod, service) {
|
if strings.HasPrefix(info.FullMethod, service) {
|
||||||
return handler(ctx, req)
|
return handler(ctx, req)
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ import (
|
|||||||
"github.com/zitadel/zitadel/internal/api/grpc/server/middleware"
|
"github.com/zitadel/zitadel/internal/api/grpc/server/middleware"
|
||||||
"github.com/zitadel/zitadel/internal/query"
|
"github.com/zitadel/zitadel/internal/query"
|
||||||
"github.com/zitadel/zitadel/internal/telemetry/metrics"
|
"github.com/zitadel/zitadel/internal/telemetry/metrics"
|
||||||
|
system_pb "github.com/zitadel/zitadel/pkg/grpc/system"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Server interface {
|
type Server interface {
|
||||||
@ -29,8 +30,7 @@ func CreateServer(verifier *authz.TokenVerifier, authConfig authz.Config, querie
|
|||||||
middleware.SentryHandler(),
|
middleware.SentryHandler(),
|
||||||
middleware.NoCacheInterceptor(),
|
middleware.NoCacheInterceptor(),
|
||||||
middleware.ErrorHandler(),
|
middleware.ErrorHandler(),
|
||||||
//TODO: Handle Ignored Services
|
middleware.InstanceInterceptor(queries, hostHeaderName, system_pb.SystemService_MethodPrefix),
|
||||||
middleware.InstanceInterceptor(queries, hostHeaderName, "/zitadel.system.v1.SystemService"),
|
|
||||||
middleware.AuthorizationInterceptor(verifier, authConfig),
|
middleware.AuthorizationInterceptor(verifier, authConfig),
|
||||||
middleware.TranslationHandler(),
|
middleware.TranslationHandler(),
|
||||||
middleware.ValidationHandler(),
|
middleware.ValidationHandler(),
|
||||||
|
@ -73,3 +73,7 @@ func StartQueries(ctx context.Context, es *eventstore.Eventstore, sqlClient *sql
|
|||||||
|
|
||||||
return repo, nil
|
return repo, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (q *Queries) Health(ctx context.Context) error {
|
||||||
|
return q.client.Ping()
|
||||||
|
}
|
||||||
|
@ -104,6 +104,10 @@ service SystemService {
|
|||||||
post: "/instances/_search"
|
post: "/instances/_search"
|
||||||
body: "*"
|
body: "*"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
option (zitadel.v1.auth_option) = {
|
||||||
|
permission: "authenticated";
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns the detail of an instance
|
// Returns the detail of an instance
|
||||||
@ -111,6 +115,10 @@ service SystemService {
|
|||||||
option (google.api.http) = {
|
option (google.api.http) = {
|
||||||
get: "/instances/{instance_id}";
|
get: "/instances/{instance_id}";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
option (zitadel.v1.auth_option) = {
|
||||||
|
permission: "authenticated";
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Creates a new instance with all needed setup data
|
// Creates a new instance with all needed setup data
|
||||||
@ -120,6 +128,10 @@ service SystemService {
|
|||||||
post: "/instances"
|
post: "/instances"
|
||||||
body: "*"
|
body: "*"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
option (zitadel.v1.auth_option) = {
|
||||||
|
permission: "authenticated";
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Removes a instances
|
// Removes a instances
|
||||||
@ -128,6 +140,10 @@ service SystemService {
|
|||||||
option (google.api.http) = {
|
option (google.api.http) = {
|
||||||
delete: "/instances/{instance_id}"
|
delete: "/instances/{instance_id}"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
option (zitadel.v1.auth_option) = {
|
||||||
|
permission: "authenticated";
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Checks if a domain exists
|
// Checks if a domain exists
|
||||||
@ -136,6 +152,10 @@ service SystemService {
|
|||||||
post: "/domains/{domain}/_exists";
|
post: "/domains/{domain}/_exists";
|
||||||
body: "*"
|
body: "*"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
option (zitadel.v1.auth_option) = {
|
||||||
|
permission: "authenticated";
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns the custom domains of an instance
|
// Returns the custom domains of an instance
|
||||||
@ -144,6 +164,10 @@ service SystemService {
|
|||||||
post: "/instances/{instance_id}/domains/_search";
|
post: "/instances/{instance_id}/domains/_search";
|
||||||
body: "*"
|
body: "*"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
option (zitadel.v1.auth_option) = {
|
||||||
|
permission: "authenticated";
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns the domain of an instance
|
// Returns the domain of an instance
|
||||||
@ -152,6 +176,10 @@ service SystemService {
|
|||||||
post: "/instances/{instance_id}/domains";
|
post: "/instances/{instance_id}/domains";
|
||||||
body: "*"
|
body: "*"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
option (zitadel.v1.auth_option) = {
|
||||||
|
permission: "authenticated";
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns the domain of an instance
|
// Returns the domain of an instance
|
||||||
@ -159,6 +187,10 @@ service SystemService {
|
|||||||
option (google.api.http) = {
|
option (google.api.http) = {
|
||||||
delete: "/instances/{instance_id}/domains/{domain}";
|
delete: "/instances/{instance_id}/domains/{domain}";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
option (zitadel.v1.auth_option) = {
|
||||||
|
permission: "authenticated";
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns the domain of an instance
|
// Returns the domain of an instance
|
||||||
@ -167,6 +199,10 @@ service SystemService {
|
|||||||
post: "/instances/{instance_id}/domains/_set_primary";
|
post: "/instances/{instance_id}/domains/_set_primary";
|
||||||
body: "*"
|
body: "*"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
option (zitadel.v1.auth_option) = {
|
||||||
|
permission: "authenticated";
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -179,6 +215,10 @@ service SystemService {
|
|||||||
body: "*"
|
body: "*"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
option (zitadel.v1.auth_option) = {
|
||||||
|
permission: "authenticated";
|
||||||
|
};
|
||||||
|
|
||||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||||
tags: "views";
|
tags: "views";
|
||||||
external_docs: {
|
external_docs: {
|
||||||
@ -203,6 +243,9 @@ service SystemService {
|
|||||||
post: "/views/{database}/{view_name}";
|
post: "/views/{database}/{view_name}";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
option (zitadel.v1.auth_option) = {
|
||||||
|
permission: "authenticated";
|
||||||
|
};
|
||||||
|
|
||||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||||
tags: "views";
|
tags: "views";
|
||||||
@ -228,6 +271,10 @@ service SystemService {
|
|||||||
body: "*"
|
body: "*"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
option (zitadel.v1.auth_option) = {
|
||||||
|
permission: "authenticated";
|
||||||
|
};
|
||||||
|
|
||||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||||
tags: "failed events";
|
tags: "failed events";
|
||||||
external_docs: {
|
external_docs: {
|
||||||
@ -253,6 +300,10 @@ service SystemService {
|
|||||||
delete: "/failedevents/{database}/{view_name}/{failed_sequence}";
|
delete: "/failedevents/{database}/{view_name}/{failed_sequence}";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
option (zitadel.v1.auth_option) = {
|
||||||
|
permission: "authenticated";
|
||||||
|
};
|
||||||
|
|
||||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||||
tags: "failed events";
|
tags: "failed events";
|
||||||
external_docs: {
|
external_docs: {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user