mirror of
https://github.com/opencloud-eu/opencloud.git
synced 2025-12-31 01:10:20 -06:00
Merge pull request #9079 from owncloud/feat/reva-app-auth
feat: reva app auth
This commit is contained in:
1
Makefile
1
Makefile
@@ -25,6 +25,7 @@ OCIS_MODULES = \
|
||||
services/app-provider \
|
||||
services/app-registry \
|
||||
services/audit \
|
||||
services/auth-app \
|
||||
services/auth-basic \
|
||||
services/auth-bearer \
|
||||
services/auth-machine \
|
||||
|
||||
5
changelog/unreleased/auth-app-service.md
Normal file
5
changelog/unreleased/auth-app-service.md
Normal file
@@ -0,0 +1,5 @@
|
||||
Enhancement: Introduce auth-app service
|
||||
|
||||
Introduce a new service, auth-app, that provides authentication and authorization services for applications.
|
||||
|
||||
https://github.com/owncloud/ocis/pull/9079
|
||||
@@ -60,7 +60,7 @@ We also suggest using the last port in your extensions' range as a debug/metrics
|
||||
| 9230-9234 | [nats]({{< ref "../nats/_index.md" >}}) |
|
||||
| 9235-9239 | [idm]({{< ref "../idm/_index.md" >}}) |
|
||||
| 9240-9244 | [app-registry]({{< ref "../app-registry/_index.md" >}}) |
|
||||
| 9245-9249 | FREE |
|
||||
| 9245-9249 | [auth-app]({{< ref "../auth-app/_index.md" >}}) |
|
||||
| 9250-9254 | [ocis server (runtime)](https://github.com/owncloud/ocis/tree/master/ocis/pkg/runtime) |
|
||||
| 9255-9259 | [postprocessing]({{< ref "../postprocessing/_index.md" >}}) |
|
||||
| 9260-9264 | [clientlog]({{< ref "../clientlog/_index.md" >}}) |
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
appProvider "github.com/owncloud/ocis/v2/services/app-provider/pkg/config"
|
||||
appRegistry "github.com/owncloud/ocis/v2/services/app-registry/pkg/config"
|
||||
audit "github.com/owncloud/ocis/v2/services/audit/pkg/config"
|
||||
authapp "github.com/owncloud/ocis/v2/services/auth-app/pkg/config"
|
||||
authbasic "github.com/owncloud/ocis/v2/services/auth-basic/pkg/config"
|
||||
authbearer "github.com/owncloud/ocis/v2/services/auth-bearer/pkg/config"
|
||||
authmachine "github.com/owncloud/ocis/v2/services/auth-machine/pkg/config"
|
||||
@@ -86,6 +87,7 @@ type Config struct {
|
||||
AppProvider *appProvider.Config `yaml:"app_provider"`
|
||||
AppRegistry *appRegistry.Config `yaml:"app_registry"`
|
||||
Audit *audit.Config `yaml:"audit"`
|
||||
AuthApp *authapp.Config `yaml:"auth_app"`
|
||||
AuthBasic *authbasic.Config `yaml:"auth_basic"`
|
||||
AuthBearer *authbearer.Config `yaml:"auth_bearer"`
|
||||
AuthMachine *authmachine.Config `yaml:"auth_machine"`
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
appProvider "github.com/owncloud/ocis/v2/services/app-provider/pkg/config/defaults"
|
||||
appRegistry "github.com/owncloud/ocis/v2/services/app-registry/pkg/config/defaults"
|
||||
audit "github.com/owncloud/ocis/v2/services/audit/pkg/config/defaults"
|
||||
authapp "github.com/owncloud/ocis/v2/services/auth-app/pkg/config/defaults"
|
||||
authbasic "github.com/owncloud/ocis/v2/services/auth-basic/pkg/config/defaults"
|
||||
authbearer "github.com/owncloud/ocis/v2/services/auth-bearer/pkg/config/defaults"
|
||||
authmachine "github.com/owncloud/ocis/v2/services/auth-machine/pkg/config/defaults"
|
||||
@@ -58,6 +59,7 @@ func DefaultConfig() *Config {
|
||||
AppProvider: appProvider.DefaultConfig(),
|
||||
AppRegistry: appRegistry.DefaultConfig(),
|
||||
Audit: audit.DefaultConfig(),
|
||||
AuthApp: authapp.DefaultConfig(),
|
||||
AuthBasic: authbasic.DefaultConfig(),
|
||||
AuthBearer: authbearer.DefaultConfig(),
|
||||
AuthMachine: authmachine.DefaultConfig(),
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
appprovider "github.com/owncloud/ocis/v2/services/app-provider/pkg/command"
|
||||
appregistry "github.com/owncloud/ocis/v2/services/app-registry/pkg/command"
|
||||
audit "github.com/owncloud/ocis/v2/services/audit/pkg/command"
|
||||
authapp "github.com/owncloud/ocis/v2/services/auth-app/pkg/command"
|
||||
authbasic "github.com/owncloud/ocis/v2/services/auth-basic/pkg/command"
|
||||
authbearer "github.com/owncloud/ocis/v2/services/auth-bearer/pkg/command"
|
||||
authmachine "github.com/owncloud/ocis/v2/services/auth-machine/pkg/command"
|
||||
@@ -78,6 +79,11 @@ var svccmds = []register.Command{
|
||||
cfg.Audit.Commons = cfg.Commons
|
||||
})
|
||||
},
|
||||
func(cfg *config.Config) *cli.Command {
|
||||
return ServiceCommand(cfg, cfg.AuthApp.Service.Name, authapp.GetCommands(cfg.AuthApp), func(_ *config.Config) {
|
||||
cfg.AuthApp.Commons = cfg.Commons
|
||||
})
|
||||
},
|
||||
func(cfg *config.Config) *cli.Command {
|
||||
return ServiceCommand(cfg, cfg.AuthBasic.Service.Name, authbasic.GetCommands(cfg.AuthBasic), func(c *config.Config) {
|
||||
cfg.AuthBasic.Commons = cfg.Commons
|
||||
@@ -266,10 +272,10 @@ var svccmds = []register.Command{
|
||||
}
|
||||
|
||||
// ServiceCommand is the entry point for the all service commands.
|
||||
func ServiceCommand(cfg *config.Config, servicename string, subcommands []*cli.Command, f func(*config.Config)) *cli.Command {
|
||||
func ServiceCommand(cfg *config.Config, serviceName string, subcommands []*cli.Command, f func(*config.Config)) *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: servicename,
|
||||
Usage: helper.SubcommandDescription(servicename),
|
||||
Name: serviceName,
|
||||
Usage: helper.SubcommandDescription(serviceName),
|
||||
Category: "services",
|
||||
Before: func(c *cli.Context) error {
|
||||
configlog.Error(parser.ParseConfig(cfg, true))
|
||||
|
||||
@@ -13,6 +13,8 @@ import (
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
authapp "github.com/owncloud/ocis/v2/services/auth-app/pkg/command"
|
||||
|
||||
"github.com/cenkalti/backoff"
|
||||
"github.com/cs3org/reva/v2/pkg/events/stream"
|
||||
"github.com/cs3org/reva/v2/pkg/logger"
|
||||
@@ -324,6 +326,11 @@ func NewService(options ...Option) (*Service, error) {
|
||||
cfg.Audit.Commons = cfg.Commons
|
||||
return audit.Execute(cfg.Audit)
|
||||
})
|
||||
areg(opts.Config.AuthApp.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error {
|
||||
cfg.AuthApp.Context = ctx
|
||||
cfg.AuthApp.Commons = cfg.Commons
|
||||
return authapp.Execute(cfg.AuthApp)
|
||||
})
|
||||
areg(opts.Config.Policies.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error {
|
||||
cfg.Policies.Context = ctx
|
||||
cfg.Policies.Commons = cfg.Commons
|
||||
|
||||
37
services/auth-app/Makefile
Normal file
37
services/auth-app/Makefile
Normal file
@@ -0,0 +1,37 @@
|
||||
SHELL := bash
|
||||
NAME := auth-app
|
||||
|
||||
include ../../.make/recursion.mk
|
||||
|
||||
############ tooling ############
|
||||
ifneq (, $(shell command -v go 2> /dev/null)) # suppress `command not found warnings` for non go targets in CI
|
||||
include ../../.bingo/Variables.mk
|
||||
endif
|
||||
|
||||
############ go tooling ############
|
||||
include ../../.make/go.mk
|
||||
|
||||
############ release ############
|
||||
include ../../.make/release.mk
|
||||
|
||||
############ docs generate ############
|
||||
include ../../.make/docs.mk
|
||||
|
||||
.PHONY: docs-generate
|
||||
docs-generate: config-docs-generate
|
||||
|
||||
############ generate ############
|
||||
include ../../.make/generate.mk
|
||||
|
||||
.PHONY: ci-go-generate
|
||||
ci-go-generate: # CI runs ci-node-generate automatically before this target
|
||||
|
||||
.PHONY: ci-node-generate
|
||||
ci-node-generate:
|
||||
|
||||
############ licenses ############
|
||||
.PHONY: ci-node-check-licenses
|
||||
ci-node-check-licenses:
|
||||
|
||||
.PHONY: ci-node-save-licenses
|
||||
ci-node-save-licenses:
|
||||
30
services/auth-app/README.md
Normal file
30
services/auth-app/README.md
Normal file
@@ -0,0 +1,30 @@
|
||||
# Auth-App
|
||||
|
||||
The auth-app service provides authentication for 3rd party apps.
|
||||
|
||||
## The `auth` Service Family
|
||||
|
||||
ocis uses serveral authentication services for different use cases. All services that start with `auth-` are part of the authentication service family. Each member authenticates requests with different scopes. As of now, these services exist:
|
||||
- `auth-app` handles authentication of external 3rd party apps
|
||||
- `auth-basic` handles basic authentication
|
||||
- `auth-bearer` handles oidc authentication
|
||||
- `auth-machine` handles interservice authentication when a user is impersonated
|
||||
- `auth-service` handles interservice authentication when using service accounts
|
||||
|
||||
## Service Startup
|
||||
|
||||
Because this service is not started automatically, a manual start needs to be initiated which can be done in several ways. To configure the service usage, an environment variable for the proxy service needs to be set to allow app authentication.
|
||||
```bash
|
||||
OCIS_ADD_RUN_SERVICES=auth-app # deployment specific. Add the service to the manual startup list, use with binary deployments. Alternatively you can start the service explicitly via the command line.
|
||||
PROXY_ENABLE_APP_AUTH=true # mandatory, allow app authentication. In case of a distributed environment, this envvar needs to be set in the proxy service.
|
||||
```
|
||||
|
||||
## App Tokens
|
||||
|
||||
App Tokens are used to authenticate 3rd party access via https like when using curl (apps) to access an API endpoint. These apps need to authenticate themselves as no logged in user authenticates the request. To be able to use an app token, one must first create a token via the cli. Replace the `user-name` with an existing user. For the `token-expiration`, you can use any time abbreviation from the following list: `h, m, s`. Examples: `72h` or `1h` or `1m` or `1s.` Default is `72h`.
|
||||
|
||||
```bash
|
||||
ocis auth-app create --user-name={user-name} --expiration={token-expiration}
|
||||
```
|
||||
|
||||
Once generated, these tokens can be used to authenticate requests to ocis. They are passed as part of the request as `Basic Auth` header.
|
||||
14
services/auth-app/cmd/auth-app/main.go
Normal file
14
services/auth-app/cmd/auth-app/main.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/owncloud/ocis/v2/services/auth-app/pkg/command"
|
||||
"github.com/owncloud/ocis/v2/services/auth-app/pkg/config/defaults"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if err := command.Execute(defaults.DefaultConfig()); err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
124
services/auth-app/pkg/command/create.go
Normal file
124
services/auth-app/pkg/command/create.go
Normal file
@@ -0,0 +1,124 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1"
|
||||
"github.com/cs3org/reva/v2/pkg/auth/scope"
|
||||
|
||||
"time"
|
||||
|
||||
applicationsv1beta1 "github.com/cs3org/go-cs3apis/cs3/auth/applications/v1beta1"
|
||||
gatewayv1beta1 "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
|
||||
userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
|
||||
rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
|
||||
typesv1beta1 "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
|
||||
ctxpkg "github.com/cs3org/reva/v2/pkg/ctx"
|
||||
"github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool"
|
||||
"github.com/owncloud/ocis/v2/ocis-pkg/config/configlog"
|
||||
"github.com/owncloud/ocis/v2/ocis-pkg/registry"
|
||||
"github.com/owncloud/ocis/v2/ocis-pkg/tracing"
|
||||
"github.com/owncloud/ocis/v2/services/auth-app/pkg/config"
|
||||
"github.com/owncloud/ocis/v2/services/auth-app/pkg/config/parser"
|
||||
"github.com/urfave/cli/v2"
|
||||
"google.golang.org/grpc/metadata"
|
||||
)
|
||||
|
||||
// Create is the entrypoint for the app auth create command
|
||||
func Create(cfg *config.Config) *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "create",
|
||||
Usage: "create an app auth token for a user",
|
||||
Category: "maintenance",
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "user-name",
|
||||
Value: "",
|
||||
Usage: "user to create the app-token for",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "expiration",
|
||||
Value: "72h",
|
||||
Usage: "expiration of the app password, e.g. 72h, 1h, 1m, 1s. Default is 72h.",
|
||||
},
|
||||
},
|
||||
Before: func(_ *cli.Context) error {
|
||||
return configlog.ReturnError(parser.ParseConfig(cfg))
|
||||
},
|
||||
Action: func(c *cli.Context) error {
|
||||
traceProvider, err := tracing.GetServiceTraceProvider(cfg.Tracing, cfg.Service.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
gatewaySelector, err := pool.GatewaySelector(
|
||||
cfg.Reva.Address,
|
||||
append(
|
||||
cfg.Reva.GetRevaOptions(),
|
||||
pool.WithRegistry(registry.GetRegistry()),
|
||||
pool.WithTracerProvider(traceProvider),
|
||||
)...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
next, err := gatewaySelector.Next()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
userName := c.String("user-name")
|
||||
if userName == "" {
|
||||
fmt.Printf("Username to create app token for: ")
|
||||
if _, err := fmt.Scanln(&userName); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
authRes, err := next.Authenticate(ctx, &gatewayv1beta1.AuthenticateRequest{
|
||||
Type: "machine",
|
||||
ClientId: "username:" + userName,
|
||||
ClientSecret: cfg.MachineAuthAPIKey,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if authRes.GetStatus().GetCode() != rpc.Code_CODE_OK {
|
||||
return fmt.Errorf("error authenticating user: %s", authRes.GetStatus().GetMessage())
|
||||
}
|
||||
|
||||
granteeCtx := ctxpkg.ContextSetUser(context.Background(), &userpb.User{Id: authRes.GetUser().GetId()})
|
||||
granteeCtx = metadata.AppendToOutgoingContext(granteeCtx, ctxpkg.TokenHeader, authRes.GetToken())
|
||||
|
||||
scopes, err := scope.AddOwnerScope(map[string]*authpb.Scope{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
expiry, err := time.ParseDuration(c.String("expiration"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
appPassword, err := next.GenerateAppPassword(granteeCtx, &applicationsv1beta1.GenerateAppPasswordRequest{
|
||||
TokenScope: scopes,
|
||||
Label: "Generated via CLI",
|
||||
Expiration: &typesv1beta1.Timestamp{
|
||||
Seconds: uint64(time.Now().Add(expiry).Unix()),
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("App token created for %s", authRes.GetUser().GetUsername())
|
||||
fmt.Println()
|
||||
fmt.Printf(" token: %s", appPassword.GetAppPassword().GetPassword())
|
||||
fmt.Println()
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
54
services/auth-app/pkg/command/health.go
Normal file
54
services/auth-app/pkg/command/health.go
Normal file
@@ -0,0 +1,54 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/owncloud/ocis/v2/ocis-pkg/config/configlog"
|
||||
"github.com/owncloud/ocis/v2/services/auth-app/pkg/config"
|
||||
"github.com/owncloud/ocis/v2/services/auth-app/pkg/config/parser"
|
||||
"github.com/owncloud/ocis/v2/services/auth-app/pkg/logging"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// Health is the entrypoint for the health command.
|
||||
func Health(cfg *config.Config) *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "health",
|
||||
Usage: "check health status",
|
||||
Category: "info",
|
||||
Before: func(_ *cli.Context) error {
|
||||
return configlog.ReturnError(parser.ParseConfig(cfg))
|
||||
},
|
||||
Action: func(_ *cli.Context) error {
|
||||
logger := logging.Configure(cfg.Service.Name, cfg.Log)
|
||||
|
||||
resp, err := http.Get(
|
||||
fmt.Sprintf(
|
||||
"http://%s/healthz",
|
||||
cfg.Debug.Addr,
|
||||
),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
logger.Fatal().
|
||||
Err(err).
|
||||
Msg("Failed to request health check")
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
logger.Fatal().
|
||||
Int("code", resp.StatusCode).
|
||||
Msg("Health seems to be in bad state")
|
||||
}
|
||||
|
||||
logger.Debug().
|
||||
Int("code", resp.StatusCode).
|
||||
Msg("Health got a good state")
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
35
services/auth-app/pkg/command/root.go
Normal file
35
services/auth-app/pkg/command/root.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/owncloud/ocis/v2/ocis-pkg/clihelper"
|
||||
"github.com/owncloud/ocis/v2/services/auth-app/pkg/config"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// GetCommands provides all commands for this service
|
||||
func GetCommands(cfg *config.Config) cli.Commands {
|
||||
return []*cli.Command{
|
||||
// start this service
|
||||
Server(cfg),
|
||||
|
||||
// interaction with this service
|
||||
Create(cfg),
|
||||
|
||||
// infos about this service
|
||||
Health(cfg),
|
||||
Version(cfg),
|
||||
}
|
||||
}
|
||||
|
||||
// Execute is the entry point for the ocis-auth-app command.
|
||||
func Execute(cfg *config.Config) error {
|
||||
app := clihelper.DefaultApp(&cli.App{
|
||||
Name: "auth-app",
|
||||
Usage: "Provide app authentication for oCIS",
|
||||
Commands: GetCommands(cfg),
|
||||
})
|
||||
|
||||
return app.Run(os.Args)
|
||||
}
|
||||
104
services/auth-app/pkg/command/server.go
Normal file
104
services/auth-app/pkg/command/server.go
Normal file
@@ -0,0 +1,104 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
|
||||
"github.com/cs3org/reva/v2/cmd/revad/runtime"
|
||||
"github.com/gofrs/uuid"
|
||||
"github.com/oklog/run"
|
||||
"github.com/owncloud/ocis/v2/ocis-pkg/config/configlog"
|
||||
"github.com/owncloud/ocis/v2/ocis-pkg/registry"
|
||||
"github.com/owncloud/ocis/v2/ocis-pkg/sync"
|
||||
"github.com/owncloud/ocis/v2/ocis-pkg/tracing"
|
||||
"github.com/owncloud/ocis/v2/ocis-pkg/version"
|
||||
"github.com/owncloud/ocis/v2/services/auth-app/pkg/config"
|
||||
"github.com/owncloud/ocis/v2/services/auth-app/pkg/config/parser"
|
||||
"github.com/owncloud/ocis/v2/services/auth-app/pkg/logging"
|
||||
"github.com/owncloud/ocis/v2/services/auth-app/pkg/revaconfig"
|
||||
"github.com/owncloud/ocis/v2/services/auth-app/pkg/server/debug"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// Server is the entry point for the server command.
|
||||
func Server(cfg *config.Config) *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "server",
|
||||
Usage: fmt.Sprintf("start the %s service without runtime (unsupervised mode)", cfg.Service.Name),
|
||||
Category: "server",
|
||||
Before: func(_ *cli.Context) error {
|
||||
return configlog.ReturnFatal(parser.ParseConfig(cfg))
|
||||
},
|
||||
Action: func(_ *cli.Context) error {
|
||||
logger := logging.Configure(cfg.Service.Name, cfg.Log)
|
||||
traceProvider, err := tracing.GetServiceTraceProvider(cfg.Tracing, cfg.Service.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
gr := run.Group{}
|
||||
ctx, cancel := defineContext(cfg)
|
||||
|
||||
defer cancel()
|
||||
|
||||
gr.Add(func() error {
|
||||
pidFile := path.Join(os.TempDir(), "revad-"+cfg.Service.Name+"-"+uuid.Must(uuid.NewV4()).String()+".pid")
|
||||
rCfg := revaconfig.AuthAppConfigFromStruct(cfg)
|
||||
reg := registry.GetRegistry()
|
||||
|
||||
runtime.RunWithOptions(rCfg, pidFile,
|
||||
runtime.WithLogger(&logger.Logger),
|
||||
runtime.WithRegistry(reg),
|
||||
runtime.WithTraceProvider(traceProvider),
|
||||
)
|
||||
|
||||
return nil
|
||||
}, func(err error) {
|
||||
logger.Error().
|
||||
Str("server", cfg.Service.Name).
|
||||
Err(err).
|
||||
Msg("Shutting down server")
|
||||
|
||||
cancel()
|
||||
os.Exit(1)
|
||||
})
|
||||
|
||||
debugServer, err := debug.Server(
|
||||
debug.Logger(logger),
|
||||
debug.Context(ctx),
|
||||
debug.Config(cfg),
|
||||
)
|
||||
if err != nil {
|
||||
logger.Info().Err(err).Str("server", "debug").Msg("Failed to initialize server")
|
||||
return err
|
||||
}
|
||||
|
||||
gr.Add(debugServer.ListenAndServe, func(_ error) {
|
||||
cancel()
|
||||
})
|
||||
|
||||
if !cfg.Supervised {
|
||||
sync.Trap(&gr, cancel)
|
||||
}
|
||||
|
||||
grpcSvc := registry.BuildGRPCService(cfg.GRPC.Namespace+"."+cfg.Service.Name, uuid.Must(uuid.NewV4()).String(), cfg.GRPC.Addr, version.GetString())
|
||||
if err := registry.RegisterService(ctx, grpcSvc, logger); err != nil {
|
||||
logger.Fatal().Err(err).Msg("failed to register the grpc service")
|
||||
}
|
||||
|
||||
return gr.Run()
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// defineContext sets the context for the service. If there is a context configured it will create a new child from it,
|
||||
// if not, it will create a root context that can be cancelled.
|
||||
func defineContext(cfg *config.Config) (context.Context, context.CancelFunc) {
|
||||
return func() (context.Context, context.CancelFunc) {
|
||||
if cfg.Context == nil {
|
||||
return context.WithCancel(context.Background())
|
||||
}
|
||||
return context.WithCancel(cfg.Context)
|
||||
}()
|
||||
}
|
||||
50
services/auth-app/pkg/command/version.go
Normal file
50
services/auth-app/pkg/command/version.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/owncloud/ocis/v2/ocis-pkg/registry"
|
||||
"github.com/owncloud/ocis/v2/ocis-pkg/version"
|
||||
|
||||
tw "github.com/olekukonko/tablewriter"
|
||||
"github.com/owncloud/ocis/v2/services/auth-app/pkg/config"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// Version prints the service versions of all running instances.
|
||||
func Version(cfg *config.Config) *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "version",
|
||||
Usage: "print the version of this binary and the running service instances",
|
||||
Category: "info",
|
||||
Action: func(c *cli.Context) error {
|
||||
fmt.Println("Version: " + version.GetString())
|
||||
fmt.Printf("Compiled: %s\n", version.Compiled())
|
||||
fmt.Println("")
|
||||
|
||||
reg := registry.GetRegistry()
|
||||
services, err := reg.GetService(cfg.GRPC.Namespace + "." + cfg.Service.Name)
|
||||
if err != nil {
|
||||
fmt.Println(fmt.Errorf("could not get %s services from the registry: %v", cfg.Service.Name, err))
|
||||
return err
|
||||
}
|
||||
|
||||
if len(services) == 0 {
|
||||
fmt.Println("No running " + cfg.Service.Name + " service found.")
|
||||
return nil
|
||||
}
|
||||
|
||||
table := tw.NewWriter(os.Stdout)
|
||||
table.SetHeader([]string{"Version", "Address", "Id"})
|
||||
table.SetAutoFormatHeaders(false)
|
||||
for _, s := range services {
|
||||
for _, n := range s.Nodes {
|
||||
table.Append([]string{s.Version, n.Address, n.Id})
|
||||
}
|
||||
}
|
||||
table.Render()
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
57
services/auth-app/pkg/config/config.go
Normal file
57
services/auth-app/pkg/config/config.go
Normal file
@@ -0,0 +1,57 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/owncloud/ocis/v2/ocis-pkg/shared"
|
||||
)
|
||||
|
||||
// Config defines the root config structure
|
||||
type Config struct {
|
||||
Commons *shared.Commons `yaml:"-"` // don't use this directly as configuration for a service
|
||||
Service Service `yaml:"-"`
|
||||
Tracing *Tracing `yaml:"tracing"`
|
||||
Log *Log `yaml:"log"`
|
||||
Debug Debug `yaml:"debug"`
|
||||
|
||||
GRPC GRPCConfig `yaml:"grpc"`
|
||||
|
||||
TokenManager *TokenManager `yaml:"token_manager"`
|
||||
Reva *shared.Reva `yaml:"reva"`
|
||||
|
||||
SkipUserGroupsInToken bool `yaml:"skip_user_groups_in_token" env:"AUTH_APP_SKIP_USER_GROUPS_IN_TOKEN" desc:"Disables the encoding of the user's group memberships in the access token. This reduces the token size, especially when users are members of a large number of groups." introductionVersion:"%%NEXT%%"`
|
||||
|
||||
MachineAuthAPIKey string `yaml:"machine_auth_api_key" env:"OCIS_MACHINE_AUTH_API_KEY;AUTH_APP_MACHINE_AUTH_API_KEY" desc:"The machine auth API key used to validate internal requests necessary to access resources from other services." introductionVersion:"%%NEXT%%"`
|
||||
|
||||
Supervised bool `yaml:"-"`
|
||||
Context context.Context `yaml:"-"`
|
||||
}
|
||||
|
||||
// Log defines the loging configuration
|
||||
type Log struct {
|
||||
Level string `yaml:"level" env:"OCIS_LOG_LEVEL;AUTH_APP_LOG_LEVEL" desc:"The log level. Valid values are: 'panic', 'fatal', 'error', 'warn', 'info', 'debug', 'trace'." introductionVersion:"%%NEXT%%"`
|
||||
Pretty bool `yaml:"pretty" env:"OCIS_LOG_PRETTY;AUTH_APP_LOG_PRETTY" desc:"Activates pretty log output." introductionVersion:"%%NEXT%%"`
|
||||
Color bool `yaml:"color" env:"OCIS_LOG_COLOR;AUTH_APP_LOG_COLOR" desc:"Activates colorized log output." introductionVersion:"%%NEXT%%"`
|
||||
File string `yaml:"file" env:"OCIS_LOG_FILE;AUTH_APP_LOG_FILE" desc:"The path to the log file. Activates logging to this file if set." introductionVersion:"%%NEXT%%"`
|
||||
}
|
||||
|
||||
// Service defines the service configuration
|
||||
type Service struct {
|
||||
Name string `yaml:"-"`
|
||||
}
|
||||
|
||||
// Debug defines the debug configuration
|
||||
type Debug struct {
|
||||
Addr string `yaml:"addr" env:"AUTH_APP_DEBUG_ADDR" desc:"Bind address of the debug server, where metrics, health, config and debug endpoints will be exposed." introductionVersion:"%%NEXT%%"`
|
||||
Token string `yaml:"token" env:"AUTH_APP_DEBUG_TOKEN" desc:"Token to secure the metrics endpoint." introductionVersion:"%%NEXT%%"`
|
||||
Pprof bool `yaml:"pprof" env:"AUTH_APP_DEBUG_PPROF" desc:"Enables pprof, which can be used for profiling." introductionVersion:"%%NEXT%%"`
|
||||
Zpages bool `yaml:"zpages" env:"AUTH_APP_DEBUG_ZPAGES" desc:"Enables zpages, which can be used for collecting and viewing traces in-memory." introductionVersion:"%%NEXT%%"`
|
||||
}
|
||||
|
||||
// GRPCConfig defines the GRPC configuration
|
||||
type GRPCConfig struct {
|
||||
Addr string `yaml:"addr" env:"AUTH_APP_GRPC_ADDR" desc:"The bind address of the GRPC service." introductionVersion:"%%NEXT%%"`
|
||||
TLS *shared.GRPCServiceTLS `yaml:"tls"`
|
||||
Namespace string `yaml:"-"`
|
||||
Protocol string `yaml:"protocol" env:"AUTH_APP_GRPC_PROTOCOL" desc:"The transport protocol of the GRPC service." introductionVersion:"%%NEXT%%"`
|
||||
}
|
||||
87
services/auth-app/pkg/config/defaults/defaultconfig.go
Normal file
87
services/auth-app/pkg/config/defaults/defaultconfig.go
Normal file
@@ -0,0 +1,87 @@
|
||||
package defaults
|
||||
|
||||
import (
|
||||
"github.com/owncloud/ocis/v2/ocis-pkg/shared"
|
||||
"github.com/owncloud/ocis/v2/ocis-pkg/structs"
|
||||
"github.com/owncloud/ocis/v2/services/auth-app/pkg/config"
|
||||
)
|
||||
|
||||
// FullDefaultConfig returns a fully initialized default configuration
|
||||
func FullDefaultConfig() *config.Config {
|
||||
cfg := DefaultConfig()
|
||||
EnsureDefaults(cfg)
|
||||
Sanitize(cfg)
|
||||
return cfg
|
||||
}
|
||||
|
||||
// DefaultConfig returns a basic default configuration
|
||||
func DefaultConfig() *config.Config {
|
||||
return &config.Config{
|
||||
Debug: config.Debug{
|
||||
Addr: "127.0.0.1:9245",
|
||||
Token: "",
|
||||
Pprof: false,
|
||||
Zpages: false,
|
||||
},
|
||||
GRPC: config.GRPCConfig{
|
||||
Addr: "127.0.0.1:9246",
|
||||
Namespace: "com.owncloud.api",
|
||||
Protocol: "tcp",
|
||||
},
|
||||
Service: config.Service{
|
||||
Name: "auth-app",
|
||||
},
|
||||
Reva: shared.DefaultRevaConfig(),
|
||||
}
|
||||
}
|
||||
|
||||
// EnsureDefaults adds default values to the configuration if they are not set yet
|
||||
func EnsureDefaults(cfg *config.Config) {
|
||||
// provide with defaults for shared logging, since we need a valid destination address for "envdecode".
|
||||
if cfg.Log == nil && cfg.Commons != nil && cfg.Commons.Log != nil {
|
||||
cfg.Log = &config.Log{
|
||||
Level: cfg.Commons.Log.Level,
|
||||
Pretty: cfg.Commons.Log.Pretty,
|
||||
Color: cfg.Commons.Log.Color,
|
||||
File: cfg.Commons.Log.File,
|
||||
}
|
||||
} else if cfg.Log == nil {
|
||||
cfg.Log = &config.Log{}
|
||||
}
|
||||
// provide with defaults for shared tracing, since we need a valid destination address for "envdecode".
|
||||
if cfg.Tracing == nil && cfg.Commons != nil && cfg.Commons.Tracing != nil {
|
||||
cfg.Tracing = &config.Tracing{
|
||||
Enabled: cfg.Commons.Tracing.Enabled,
|
||||
Type: cfg.Commons.Tracing.Type,
|
||||
Endpoint: cfg.Commons.Tracing.Endpoint,
|
||||
Collector: cfg.Commons.Tracing.Collector,
|
||||
}
|
||||
} else if cfg.Tracing == nil {
|
||||
cfg.Tracing = &config.Tracing{}
|
||||
}
|
||||
|
||||
if cfg.Reva == nil && cfg.Commons != nil {
|
||||
cfg.Reva = structs.CopyOrZeroValue(cfg.Commons.Reva)
|
||||
}
|
||||
|
||||
if cfg.MachineAuthAPIKey == "" && cfg.Commons != nil && cfg.Commons.MachineAuthAPIKey != "" {
|
||||
cfg.MachineAuthAPIKey = cfg.Commons.MachineAuthAPIKey
|
||||
}
|
||||
|
||||
if cfg.TokenManager == nil && cfg.Commons != nil && cfg.Commons.TokenManager != nil {
|
||||
cfg.TokenManager = &config.TokenManager{
|
||||
JWTSecret: cfg.Commons.TokenManager.JWTSecret,
|
||||
}
|
||||
} else if cfg.TokenManager == nil {
|
||||
cfg.TokenManager = &config.TokenManager{}
|
||||
}
|
||||
|
||||
if cfg.GRPC.TLS == nil && cfg.Commons != nil {
|
||||
cfg.GRPC.TLS = structs.CopyOrZeroValue(cfg.Commons.GRPCServiceTLS)
|
||||
}
|
||||
}
|
||||
|
||||
// Sanitize sanitized the configuration
|
||||
func Sanitize(_ *config.Config) {
|
||||
// nothing to sanitize here atm
|
||||
}
|
||||
42
services/auth-app/pkg/config/parser/parse.go
Normal file
42
services/auth-app/pkg/config/parser/parse.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config"
|
||||
"github.com/owncloud/ocis/v2/ocis-pkg/shared"
|
||||
"github.com/owncloud/ocis/v2/services/auth-app/pkg/config"
|
||||
"github.com/owncloud/ocis/v2/services/auth-app/pkg/config/defaults"
|
||||
|
||||
"github.com/owncloud/ocis/v2/ocis-pkg/config/envdecode"
|
||||
)
|
||||
|
||||
// ParseConfig loads configuration from known paths.
|
||||
func ParseConfig(cfg *config.Config) error {
|
||||
err := ociscfg.BindSourcesToStructs(cfg.Service.Name, cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defaults.EnsureDefaults(cfg)
|
||||
|
||||
// load all env variables relevant to the config in the current context.
|
||||
if err := envdecode.Decode(cfg); err != nil {
|
||||
// no environment variable set for this config is an expected "error"
|
||||
if !errors.Is(err, envdecode.ErrNoTargetFieldsAreSet) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
defaults.Sanitize(cfg)
|
||||
|
||||
return Validate(cfg)
|
||||
}
|
||||
|
||||
func Validate(cfg *config.Config) error {
|
||||
if cfg.TokenManager.JWTSecret == "" {
|
||||
return shared.MissingJWTTokenError(cfg.Service.Name)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
6
services/auth-app/pkg/config/reva.go
Normal file
6
services/auth-app/pkg/config/reva.go
Normal file
@@ -0,0 +1,6 @@
|
||||
package config
|
||||
|
||||
// TokenManager is the config for using the reva token manager
|
||||
type TokenManager struct {
|
||||
JWTSecret string `yaml:"jwt_secret" env:"OCIS_JWT_SECRET;AUTH_APP_JWT_SECRET" desc:"The secret to mint and validate jwt tokens." introductionVersion:"%%NEXT%%"`
|
||||
}
|
||||
21
services/auth-app/pkg/config/tracing.go
Normal file
21
services/auth-app/pkg/config/tracing.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package config
|
||||
|
||||
import "github.com/owncloud/ocis/v2/ocis-pkg/tracing"
|
||||
|
||||
// Tracing defines the tracing configuration.
|
||||
type Tracing struct {
|
||||
Enabled bool `yaml:"enabled" env:"OCIS_TRACING_ENABLED;AUTH_APP_TRACING_ENABLED" desc:"Activates tracing." introductionVersion:"%%NEXT%%"`
|
||||
Type string `yaml:"type" env:"OCIS_TRACING_TYPE;AUTH_APP_TRACING_TYPE" desc:"The type of tracing. Defaults to '', which is the same as 'jaeger'. Allowed tracing types are 'jaeger' and '' as of now." introductionVersion:"%%NEXT%%"`
|
||||
Endpoint string `yaml:"endpoint" env:"OCIS_TRACING_ENDPOINT;AUTH_APP_TRACING_ENDPOINT" desc:"The endpoint of the tracing agent." introductionVersion:"%%NEXT%%"`
|
||||
Collector string `yaml:"collector" env:"OCIS_TRACING_COLLECTOR;AUTH_APP_TRACING_COLLECTOR" desc:"The HTTP endpoint for sending spans directly to a collector, i.e. http://jaeger-collector:14268/api/traces. Only used if the tracing endpoint is unset." introductionVersion:"%%NEXT%%"`
|
||||
}
|
||||
|
||||
// Convert Tracing to the tracing package's Config struct.
|
||||
func (t Tracing) Convert() tracing.Config {
|
||||
return tracing.Config{
|
||||
Enabled: t.Enabled,
|
||||
Type: t.Type,
|
||||
Endpoint: t.Endpoint,
|
||||
Collector: t.Collector,
|
||||
}
|
||||
}
|
||||
17
services/auth-app/pkg/logging/logging.go
Normal file
17
services/auth-app/pkg/logging/logging.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package logging
|
||||
|
||||
import (
|
||||
"github.com/owncloud/ocis/v2/ocis-pkg/log"
|
||||
"github.com/owncloud/ocis/v2/services/auth-app/pkg/config"
|
||||
)
|
||||
|
||||
// Configure initializes a service-specific logger instance.
|
||||
func Configure(name string, cfg *config.Log) log.Logger {
|
||||
return log.NewLogger(
|
||||
log.Name(name),
|
||||
log.Level(cfg.Level),
|
||||
log.Pretty(cfg.Pretty),
|
||||
log.Color(cfg.Color),
|
||||
log.File(cfg.File),
|
||||
)
|
||||
}
|
||||
56
services/auth-app/pkg/revaconfig/config.go
Normal file
56
services/auth-app/pkg/revaconfig/config.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package revaconfig
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
|
||||
"github.com/owncloud/ocis/v2/ocis-pkg/config/defaults"
|
||||
"github.com/owncloud/ocis/v2/services/auth-app/pkg/config"
|
||||
)
|
||||
|
||||
// AuthAppConfigFromStruct will adapt an oCIS config struct into a reva mapstructure to start a reva service.
|
||||
func AuthAppConfigFromStruct(cfg *config.Config) map[string]interface{} {
|
||||
appAuthJSON := filepath.Join(defaults.BaseDataPath(), "appauth.json")
|
||||
|
||||
rcfg := map[string]interface{}{
|
||||
"shared": map[string]interface{}{
|
||||
"jwt_secret": cfg.TokenManager.JWTSecret,
|
||||
"gatewaysvc": cfg.Reva.Address,
|
||||
"skip_user_groups_in_token": cfg.SkipUserGroupsInToken,
|
||||
"grpc_client_options": cfg.Reva.GetGRPCClientConfig(),
|
||||
},
|
||||
"grpc": map[string]interface{}{
|
||||
"network": cfg.GRPC.Protocol,
|
||||
"address": cfg.GRPC.Addr,
|
||||
"tls_settings": map[string]interface{}{
|
||||
"enabled": cfg.GRPC.TLS.Enabled,
|
||||
"certificate": cfg.GRPC.TLS.Cert,
|
||||
"key": cfg.GRPC.TLS.Key,
|
||||
},
|
||||
"services": map[string]interface{}{
|
||||
"authprovider": map[string]interface{}{
|
||||
"auth_manager": "appauth",
|
||||
"auth_managers": map[string]interface{}{
|
||||
"appauth": map[string]interface{}{
|
||||
"gateway_addr": cfg.Reva.Address,
|
||||
},
|
||||
},
|
||||
},
|
||||
"applicationauth": map[string]interface{}{
|
||||
"driver": "json",
|
||||
"drivers": map[string]interface{}{
|
||||
"json": map[string]interface{}{
|
||||
"file": appAuthJSON,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"interceptors": map[string]interface{}{
|
||||
"prometheus": map[string]interface{}{
|
||||
"namespace": "ocis",
|
||||
"subsystem": "auth_app",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
return rcfg
|
||||
}
|
||||
50
services/auth-app/pkg/server/debug/option.go
Normal file
50
services/auth-app/pkg/server/debug/option.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package debug
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/owncloud/ocis/v2/ocis-pkg/log"
|
||||
"github.com/owncloud/ocis/v2/services/auth-app/pkg/config"
|
||||
)
|
||||
|
||||
// Option defines a single option function.
|
||||
type Option func(o *Options)
|
||||
|
||||
// Options defines the available options for this package.
|
||||
type Options struct {
|
||||
Logger log.Logger
|
||||
Context context.Context
|
||||
Config *config.Config
|
||||
}
|
||||
|
||||
// newOptions initializes the available default options.
|
||||
func newOptions(opts ...Option) Options {
|
||||
opt := Options{}
|
||||
|
||||
for _, o := range opts {
|
||||
o(&opt)
|
||||
}
|
||||
|
||||
return opt
|
||||
}
|
||||
|
||||
// Logger provides a function to set the logger option.
|
||||
func Logger(val log.Logger) Option {
|
||||
return func(o *Options) {
|
||||
o.Logger = val
|
||||
}
|
||||
}
|
||||
|
||||
// Context provides a function to set the context option.
|
||||
func Context(val context.Context) Option {
|
||||
return func(o *Options) {
|
||||
o.Context = val
|
||||
}
|
||||
}
|
||||
|
||||
// Config provides a function to set the config option.
|
||||
func Config(val *config.Config) Option {
|
||||
return func(o *Options) {
|
||||
o.Config = val
|
||||
}
|
||||
}
|
||||
63
services/auth-app/pkg/server/debug/server.go
Normal file
63
services/auth-app/pkg/server/debug/server.go
Normal file
@@ -0,0 +1,63 @@
|
||||
package debug
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/owncloud/ocis/v2/ocis-pkg/service/debug"
|
||||
"github.com/owncloud/ocis/v2/ocis-pkg/version"
|
||||
"github.com/owncloud/ocis/v2/services/auth-app/pkg/config"
|
||||
)
|
||||
|
||||
// Server initializes the debug service and server.
|
||||
func Server(opts ...Option) (*http.Server, error) {
|
||||
options := newOptions(opts...)
|
||||
|
||||
return debug.NewService(
|
||||
debug.Logger(options.Logger),
|
||||
debug.Name(options.Config.Service.Name),
|
||||
debug.Version(version.GetString()),
|
||||
debug.Address(options.Config.Debug.Addr),
|
||||
debug.Token(options.Config.Debug.Token),
|
||||
debug.Pprof(options.Config.Debug.Pprof),
|
||||
debug.Zpages(options.Config.Debug.Zpages),
|
||||
debug.Health(health(options.Config)),
|
||||
debug.Ready(ready(options.Config)),
|
||||
//debug.CorsAllowedOrigins(options.Config.HTTP.CORS.AllowedOrigins),
|
||||
//debug.CorsAllowedMethods(options.Config.HTTP.CORS.AllowedMethods),
|
||||
//debug.CorsAllowedHeaders(options.Config.HTTP.CORS.AllowedHeaders),
|
||||
//debug.CorsAllowCredentials(options.Config.HTTP.CORS.AllowCredentials),
|
||||
), nil
|
||||
}
|
||||
|
||||
// health implements the health check.
|
||||
func health(_ *config.Config) func(http.ResponseWriter, *http.Request) {
|
||||
return func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.Header().Set("Content-Type", "text/plain")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
|
||||
// TODO: check if services are up and running
|
||||
|
||||
_, err := io.WriteString(w, http.StatusText(http.StatusOK))
|
||||
// io.WriteString should not fail but if it does, we want to know.
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ready implements the ready check.
|
||||
func ready(_ *config.Config) func(http.ResponseWriter, *http.Request) {
|
||||
return func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.Header().Set("Content-Type", "text/plain")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
|
||||
// TODO: check if services are up and running
|
||||
|
||||
_, err := io.WriteString(w, http.StatusText(http.StatusOK))
|
||||
// io.WriteString should not fail but if it does, we want to know.
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ To enable `auth-basic`, you first must set `PROXY_ENABLE_BASIC_AUTH` to `true`.
|
||||
## The `auth` Service Family
|
||||
|
||||
ocis uses serveral authentication services for different use cases. All services that start with `auth-` are part of the authentication service family. Each member authenticates requests with different scopes. As of now, these services exist:
|
||||
- `auth-app` handles authentication of external 3rd party apps
|
||||
- `auth-basic` handles basic authentication
|
||||
- `auth-bearer` handles oidc authentication
|
||||
- `auth-machine` handles interservice authentication when a user is impersonated
|
||||
|
||||
@@ -5,6 +5,7 @@ The oCIS Auth Bearer service communicates with the configured OpenID Connect ide
|
||||
## The `auth` Service Family
|
||||
|
||||
ocis uses serveral authentication services for different use cases. All services that start with `auth-` are part of the authentication service family. Each member authenticates requests with different scopes. As of now, these services exist:
|
||||
- `auth-app` handles authentication of external 3rd party apps
|
||||
- `auth-basic` handles basic authentication
|
||||
- `auth-bearer` handles oidc authentication
|
||||
- `auth-machine` handles interservice authentication when a user is impersonated
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
The oCIS Auth Machine is used for interservice communication when using user impersonation.
|
||||
|
||||
ocis uses serveral authentication services for different use cases. All services that start with `auth-` are part of the authentication service family. Each member authenticates requests with different scopes. As of now, these services exist:
|
||||
- `auth-app` handles authentication of external 3rd party apps
|
||||
- `auth-basic` handles basic authentication
|
||||
- `auth-bearer` handles oidc authentication
|
||||
- `auth-machine` handles interservice authentication when a user is impersonated
|
||||
|
||||
@@ -5,6 +5,7 @@ The ocis Auth Service is used to authenticate service accounts. Compared to norm
|
||||
## The `auth` Service Family
|
||||
|
||||
ocis uses serveral authentication services for different use cases. All services that start with `auth-` are part of the authentication service family. Each member authenticates requests with different scopes. As of now, these services exist:
|
||||
- `auth-app` handles authentication of external 3rd party apps
|
||||
- `auth-basic` handles basic authentication
|
||||
- `auth-bearer` handles oidc authentication
|
||||
- `auth-machine` handles interservice authentication when a user is impersonated
|
||||
|
||||
@@ -35,6 +35,7 @@ type Config struct {
|
||||
GroupsEndpoint string `yaml:"-"`
|
||||
PermissionsEndpoint string `yaml:"-"`
|
||||
SharingEndpoint string `yaml:"-"`
|
||||
AuthAppEndpoint string `yaml:"-"`
|
||||
AuthBasicEndpoint string `yaml:"-"`
|
||||
AuthBearerEndpoint string `yaml:"-"`
|
||||
AuthMachineEndpoint string `yaml:"-"`
|
||||
|
||||
@@ -52,6 +52,7 @@ func DefaultConfig() *config.Config {
|
||||
FrontendPublicURL: "https://localhost:9200",
|
||||
|
||||
AppRegistryEndpoint: "com.owncloud.api.app-registry",
|
||||
AuthAppEndpoint: "com.owncloud.api.auth-app",
|
||||
AuthBasicEndpoint: "com.owncloud.api.auth-basic",
|
||||
AuthMachineEndpoint: "com.owncloud.api.auth-machine",
|
||||
AuthServiceEndpoint: "com.owncloud.api.auth-service",
|
||||
|
||||
@@ -37,6 +37,7 @@ func GatewayConfigFromStruct(cfg *config.Config, logger log.Logger) map[string]i
|
||||
// TODO build services dynamically
|
||||
"services": map[string]interface{}{
|
||||
"gateway": map[string]interface{}{
|
||||
"applicationauthsvc": cfg.AuthAppEndpoint,
|
||||
// registries are located on the gateway
|
||||
"authregistrysvc": cfg.Reva.Address,
|
||||
"storageregistrysvc": cfg.Reva.Address,
|
||||
@@ -89,6 +90,7 @@ func GatewayConfigFromStruct(cfg *config.Config, logger log.Logger) map[string]i
|
||||
"drivers": map[string]interface{}{
|
||||
"static": map[string]interface{}{
|
||||
"rules": map[string]interface{}{
|
||||
"appauth": cfg.AuthAppEndpoint,
|
||||
"basic": cfg.AuthBasicEndpoint,
|
||||
"machine": cfg.AuthMachineEndpoint,
|
||||
"publicshares": cfg.StoragePublicLinkEndpoint,
|
||||
|
||||
@@ -194,6 +194,7 @@ func Server(cfg *config.Config) *cli.Command {
|
||||
|
||||
{
|
||||
middlewares := loadMiddlewares(logger, cfg, userInfoCache, signingKeyStore, traceProvider, *m, userProvider, gatewaySelector)
|
||||
|
||||
server, err := proxyHTTP.Server(
|
||||
proxyHTTP.Handler(lh.Handler()),
|
||||
proxyHTTP.Logger(logger),
|
||||
@@ -293,6 +294,12 @@ func loadMiddlewares(logger log.Logger, cfg *config.Config,
|
||||
})
|
||||
}
|
||||
|
||||
if cfg.AuthMiddleware.AllowAppAuth {
|
||||
authenticators = append(authenticators, middleware.AppAuthAuthenticator{
|
||||
Logger: logger,
|
||||
RevaGatewaySelector: gatewaySelector,
|
||||
})
|
||||
}
|
||||
authenticators = append(authenticators, middleware.NewOIDCAuthenticator(
|
||||
middleware.Logger(logger),
|
||||
middleware.UserInfoCache(userInfoCache),
|
||||
|
||||
@@ -91,6 +91,7 @@ var (
|
||||
// AuthMiddleware configures the proxy http auth middleware.
|
||||
type AuthMiddleware struct {
|
||||
CredentialsByUserAgent map[string]string `yaml:"credentials_by_user_agent"`
|
||||
AllowAppAuth bool `yaml:"allow_app_auth" env:"PROXY_ENABLE_APP_AUTH" desc:"Allow app authentication. This can be used to authenticate 3rd party applications. Note that auth-app service must be running for this feature to work." introductionVersion:"%%NEXT%%"`
|
||||
}
|
||||
|
||||
// PoliciesMiddleware configures the proxy's policies middleware.
|
||||
|
||||
53
services/proxy/pkg/middleware/app_auth.go
Normal file
53
services/proxy/pkg/middleware/app_auth.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
|
||||
cs3rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
|
||||
revactx "github.com/cs3org/reva/v2/pkg/ctx"
|
||||
"github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool"
|
||||
"github.com/owncloud/ocis/v2/ocis-pkg/log"
|
||||
)
|
||||
|
||||
// AppAuthAuthenticator defines the app auth authenticator
|
||||
type AppAuthAuthenticator struct {
|
||||
Logger log.Logger
|
||||
RevaGatewaySelector pool.Selectable[gateway.GatewayAPIClient]
|
||||
}
|
||||
|
||||
// Authenticate implements the authenticator interface to authenticate requests via app auth.
|
||||
func (m AppAuthAuthenticator) Authenticate(r *http.Request) (*http.Request, bool) {
|
||||
if isPublicPath(r.URL.Path) {
|
||||
// The authentication of public path requests is handled by another authenticator.
|
||||
// Since we can't guarantee the order of execution of the authenticators, we better
|
||||
// implement an early return here for paths we can't authenticate in this authenticator.
|
||||
return nil, false
|
||||
}
|
||||
|
||||
username, password, ok := r.BasicAuth()
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
next, err := m.RevaGatewaySelector.Next()
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
authenticateResponse, err := next.Authenticate(r.Context(), &gateway.AuthenticateRequest{
|
||||
Type: "appauth",
|
||||
ClientId: username,
|
||||
ClientSecret: password,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
if authenticateResponse.GetStatus().GetCode() != cs3rpc.Code_CODE_OK {
|
||||
// TODO: log???
|
||||
return nil, false
|
||||
}
|
||||
|
||||
r.Header.Set(revactx.TokenHeader, authenticateResponse.GetToken())
|
||||
|
||||
return r, true
|
||||
}
|
||||
69
services/proxy/pkg/middleware/app_auth_test.go
Normal file
69
services/proxy/pkg/middleware/app_auth_test.go
Normal file
@@ -0,0 +1,69 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
|
||||
gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
|
||||
rpcv1beta1 "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
|
||||
"github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool"
|
||||
"google.golang.org/grpc"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/owncloud/ocis/v2/ocis-pkg/log"
|
||||
)
|
||||
|
||||
var _ = Describe("Authenticating requests", Label("AppAuthAuthenticator"), func() {
|
||||
var authenticator Authenticator
|
||||
BeforeEach(func() {
|
||||
pool.RemoveSelector("GatewaySelector" + "com.owncloud.api.gateway")
|
||||
authenticator = AppAuthAuthenticator{
|
||||
Logger: log.NewLogger(),
|
||||
RevaGatewaySelector: pool.GetSelector[gateway.GatewayAPIClient](
|
||||
"GatewaySelector",
|
||||
"com.owncloud.api.gateway",
|
||||
func(cc grpc.ClientConnInterface) gateway.GatewayAPIClient {
|
||||
return mockGatewayClient{
|
||||
AuthenticateFunc: func(authType, clientID, clientSecret string) (string, rpcv1beta1.Code) {
|
||||
if authType != "appauth" {
|
||||
return "", rpcv1beta1.Code_CODE_NOT_FOUND
|
||||
}
|
||||
|
||||
if clientID == "test-user" && clientSecret == "AppPassword" {
|
||||
return "reva-token", rpcv1beta1.Code_CODE_OK
|
||||
}
|
||||
|
||||
return "", rpcv1beta1.Code_CODE_NOT_FOUND
|
||||
},
|
||||
}
|
||||
},
|
||||
),
|
||||
}
|
||||
})
|
||||
|
||||
When("the request contains correct data", func() {
|
||||
It("should successfully authenticate", func() {
|
||||
req := httptest.NewRequest(http.MethodGet, "http://example.com/example/path", http.NoBody)
|
||||
req.SetBasicAuth("test-user", "AppPassword")
|
||||
|
||||
req2, valid := authenticator.Authenticate(req)
|
||||
|
||||
Expect(valid).To(Equal(true))
|
||||
Expect(req2).ToNot(BeNil())
|
||||
Expect(req2.Header.Get("x-access-token")).To(Equal("reva-token"))
|
||||
})
|
||||
})
|
||||
|
||||
When("the request contains incorrect data", func() {
|
||||
It("should not successfully authenticate", func() {
|
||||
req := httptest.NewRequest(http.MethodGet, "http://example.com/example/path", http.NoBody)
|
||||
req.SetBasicAuth("test-user", "WrongAppPassword")
|
||||
|
||||
req2, valid := authenticator.Authenticate(req)
|
||||
|
||||
Expect(valid).To(Equal(false))
|
||||
Expect(req2).To(BeNil())
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
var _ = Describe("Authenticating requests", Label("PublicShareAuthenticator"), func() {
|
||||
var authenticator Authenticator
|
||||
BeforeEach(func() {
|
||||
pool.RemoveSelector("GatewaySelector" + "com.owncloud.api.gateway")
|
||||
authenticator = PublicShareAuthenticator{
|
||||
Logger: log.NewLogger(),
|
||||
RevaGatewaySelector: pool.GetSelector[gateway.GatewayAPIClient](
|
||||
|
||||
Reference in New Issue
Block a user