snapshot of https://github.com/ory/keto.git (master, 2026-09-16; blobs > 131072B omitted)
Some checks failed
CI / Run lints and checks (push) Failing after 47s
CI / Run tests (push) Failing after 1s
CI / Run race tests (push) Failing after 5s
CI / Build CLI docs (push) Has been skipped
CI / Run buf actions (push) Failing after 5s
CodeQL / Analyze (go) (push) Failing after 3s
CodeQL / Analyze (javascript) (push) Failing after 2s
Docker Image Scanners / scanners (push) Failing after 3s
Format / format (push) Failing after 3s
Synchronize Issue Labels / Synchronize Issue Labels (push) Has been skipped
CI / Generate release (push) Has been skipped
CI / Draft newsletter (push) Has been skipped
CI / Pending approval Slack notification (push) Has been skipped
CI / Send newsletter (push) Has been skipped
Some checks failed
CI / Run lints and checks (push) Failing after 47s
CI / Run tests (push) Failing after 1s
CI / Run race tests (push) Failing after 5s
CI / Build CLI docs (push) Has been skipped
CI / Run buf actions (push) Failing after 5s
CodeQL / Analyze (go) (push) Failing after 3s
CodeQL / Analyze (javascript) (push) Failing after 2s
Docker Image Scanners / scanners (push) Failing after 3s
Format / format (push) Failing after 3s
Synchronize Issue Labels / Synchronize Issue Labels (push) Has been skipped
CI / Generate release (push) Has been skipped
CI / Draft newsletter (push) Has been skipped
CI / Pending approval Slack notification (push) Has been skipped
CI / Send newsletter (push) Has been skipped
This commit is contained in:
1
oryx/configx/.snapshots/TestKoanfSchemaDefaults.json
Normal file
1
oryx/configx/.snapshots/TestKoanfSchemaDefaults.json
Normal file
@@ -0,0 +1 @@
|
||||
{}
|
||||
22
oryx/configx/context.go
Normal file
22
oryx/configx/context.go
Normal file
@@ -0,0 +1,22 @@
|
||||
// Copyright © 2023 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package configx
|
||||
|
||||
import "context"
|
||||
|
||||
type contextKey int
|
||||
|
||||
const configContextKey contextKey = iota + 1
|
||||
|
||||
func ContextWithConfigOptions(ctx context.Context, opts ...OptionModifier) context.Context {
|
||||
return context.WithValue(ctx, configContextKey, opts)
|
||||
}
|
||||
|
||||
func ConfigOptionsFromContext(ctx context.Context) []OptionModifier {
|
||||
opts, ok := ctx.Value(configContextKey).([]OptionModifier)
|
||||
if !ok {
|
||||
return []OptionModifier{}
|
||||
}
|
||||
return opts
|
||||
}
|
||||
41
oryx/configx/cors.go
Normal file
41
oryx/configx/cors.go
Normal file
@@ -0,0 +1,41 @@
|
||||
// Copyright © 2025 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package configx
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"net/http"
|
||||
|
||||
"github.com/rs/cors"
|
||||
|
||||
"github.com/ory/x/corsx"
|
||||
)
|
||||
|
||||
const CORSConfigSchemaID = "ory://cors-config"
|
||||
|
||||
//go:embed cors.schema.json
|
||||
var CORSConfigSchema []byte
|
||||
|
||||
func (p *Provider) CORS(prefix string, defaults cors.Options) (cors.Options, bool) {
|
||||
prefix = cleanPrefix(prefix)
|
||||
allowedOrigins := p.StringsF(prefix+"cors.allowed_origins", defaults.AllowedOrigins)
|
||||
allowInsecureOrigins := p.BoolF("feature_flags.legacy_allow_insecure_origins", false)
|
||||
return cors.Options{
|
||||
// Populated even though rs/cors ignores it once AllowOriginVaryRequestFunc
|
||||
// is set: some consumers read AllowedOrigins directly — notably hydra's
|
||||
// oauth2cors.Middleware (which builds its own matcher and treats an empty
|
||||
// list as "allow all", a CORS bypass) and oathkeeper's address detection.
|
||||
AllowedOrigins: p.StringsF(prefix+"cors.allowed_origins", defaults.AllowedOrigins),
|
||||
AllowedMethods: p.StringsF(prefix+"cors.allowed_methods", defaults.AllowedMethods),
|
||||
AllowedHeaders: p.StringsF(prefix+"cors.allowed_headers", defaults.AllowedHeaders),
|
||||
ExposedHeaders: p.StringsF(prefix+"cors.exposed_headers", defaults.ExposedHeaders),
|
||||
AllowCredentials: p.BoolF(prefix+"cors.allow_credentials", defaults.AllowCredentials),
|
||||
OptionsPassthrough: p.BoolF(prefix+"cors.options_passthrough", defaults.OptionsPassthrough),
|
||||
MaxAge: p.IntF(prefix+"cors.max_age", defaults.MaxAge),
|
||||
Debug: p.BoolF(prefix+"cors.debug", defaults.Debug),
|
||||
AllowOriginVaryRequestFunc: func(_ *http.Request, origin string) (bool, []string) {
|
||||
return corsx.CheckOrigin(allowedOrigins, origin, allowInsecureOrigins), nil
|
||||
},
|
||||
}, p.Bool(prefix + "cors.enabled")
|
||||
}
|
||||
106
oryx/configx/cors.schema.json
Normal file
106
oryx/configx/cors.schema.json
Normal file
@@ -0,0 +1,106 @@
|
||||
{
|
||||
"$id": "https://github.com/ory/x/configx/cors.schema.json",
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"type": "object",
|
||||
"title": "CORS",
|
||||
"description": "Configures Cross Origin Resource Sharing for this endpoint.",
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"allowed_origins": {
|
||||
"type": "array",
|
||||
"description": "A list of origins a cross-domain request can be executed from. If the special * value is present in the list, all origins will be allowed. An origin may contain a wildcard (*) to replace 0 or more characters (i.e.: https://*.example.com). Only one wildcard can be used per origin.",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"not": {
|
||||
"type": "string",
|
||||
"description": "matches all strings that contain two or more (*)",
|
||||
"pattern": ".*\\*.*\\*.*"
|
||||
},
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"format": "uri"
|
||||
},
|
||||
{
|
||||
"const": "*"
|
||||
}
|
||||
]
|
||||
},
|
||||
"uniqueItems": true,
|
||||
"examples": [
|
||||
[
|
||||
"https://example.com",
|
||||
"https://*.example.com",
|
||||
"https://*.foo.example.com"
|
||||
]
|
||||
]
|
||||
},
|
||||
"allowed_methods": {
|
||||
"type": "array",
|
||||
"description": "A list of HTTP methods the user agent is allowed to use with cross-domain requests.",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"POST",
|
||||
"GET",
|
||||
"PUT",
|
||||
"PATCH",
|
||||
"DELETE",
|
||||
"CONNECT",
|
||||
"HEAD",
|
||||
"OPTIONS",
|
||||
"TRACE"
|
||||
]
|
||||
}
|
||||
},
|
||||
"allowed_headers": {
|
||||
"type": "array",
|
||||
"description": "A list of non-simple headers the client is allowed to use with cross-domain requests.",
|
||||
"examples": [
|
||||
[
|
||||
"Authorization",
|
||||
"Content-Type",
|
||||
"Max-Age",
|
||||
"X-Session-Token",
|
||||
"X-XSRF-TOKEN",
|
||||
"X-CSRF-TOKEN"
|
||||
]
|
||||
],
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"exposed_headers": {
|
||||
"type": "array",
|
||||
"description": "Sets which headers are safe to expose to the API of a CORS API specification.",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"allow_credentials": {
|
||||
"type": "boolean",
|
||||
"description": "Sets whether the request can include user credentials like cookies, HTTP authentication or client side SSL certificates.",
|
||||
"default": true
|
||||
},
|
||||
"options_passthrough": {
|
||||
"type": "boolean",
|
||||
"description": "TODO",
|
||||
"default": false
|
||||
},
|
||||
"max_age": {
|
||||
"type": "integer",
|
||||
"description": "Sets how long (in seconds) the results of a preflight request can be cached. If set to 0, every request is preceded by a preflight request.",
|
||||
"default": 0,
|
||||
"minimum": 0
|
||||
},
|
||||
"debug": {
|
||||
"type": "boolean",
|
||||
"description": "Adds additional log output to debug CORS issues.",
|
||||
"default": false
|
||||
}
|
||||
}
|
||||
}
|
||||
30
oryx/configx/error.go
Normal file
30
oryx/configx/error.go
Normal file
@@ -0,0 +1,30 @@
|
||||
// Copyright © 2023 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package configx
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type ImmutableError struct {
|
||||
From interface{}
|
||||
To interface{}
|
||||
Key string
|
||||
error
|
||||
}
|
||||
|
||||
func NewImmutableError(key string, from, to interface{}) error {
|
||||
return &ImmutableError{
|
||||
From: from,
|
||||
To: to,
|
||||
Key: key,
|
||||
error: errors.Errorf("immutable configuration key \"%s\" was changed from \"%v\" to \"%v\"", key, from, to),
|
||||
}
|
||||
}
|
||||
|
||||
func (e *ImmutableError) Error() string {
|
||||
return fmt.Sprintf("immutable configuration key \"%s\" was changed from \"%v\" to \"%v\"", e.Key, e.From, e.To)
|
||||
}
|
||||
51
oryx/configx/helpers.go
Normal file
51
oryx/configx/helpers.go
Normal file
@@ -0,0 +1,51 @@
|
||||
// Copyright © 2023 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package configx
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
// RegisterFlags registers the config file flag.
|
||||
func RegisterFlags(flags *pflag.FlagSet) {
|
||||
flags.StringSliceP("config", "c", []string{}, "Path to one or more .json, .yaml, .yml, .toml config files. Values are loaded in the order provided, meaning that the last config file overwrites values from the previous config file.")
|
||||
}
|
||||
|
||||
// host = unix:/path/to/socket => port is discarded, otherwise format as host:port
|
||||
func GetAddress(host string, port int) string {
|
||||
if strings.HasPrefix(host, "unix:") {
|
||||
return host
|
||||
}
|
||||
return fmt.Sprintf("%s:%d", host, port)
|
||||
}
|
||||
|
||||
func (s *Serve) GetAddress() string {
|
||||
return GetAddress(s.Host, s.Port)
|
||||
}
|
||||
|
||||
// AddSchemaResources adds the config schema partials to the compiler.
|
||||
// The interface is specified instead of `jsonschema.Compiler` to allow the use of any jsonschema library fork or version.
|
||||
func AddSchemaResources(c interface {
|
||||
AddResource(url string, r io.Reader) error
|
||||
}) error {
|
||||
if err := c.AddResource(TLSConfigSchemaID, bytes.NewReader(TLSConfigSchema)); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.AddResource(ServeConfigSchemaID, bytes.NewReader(ServeConfigSchema)); err != nil {
|
||||
return err
|
||||
}
|
||||
return c.AddResource(CORSConfigSchemaID, bytes.NewReader(CORSConfigSchema))
|
||||
}
|
||||
|
||||
func cleanPrefix(prefix string) string {
|
||||
if len(prefix) > 0 {
|
||||
prefix = strings.TrimRight(prefix, ".") + "."
|
||||
}
|
||||
return prefix
|
||||
}
|
||||
69
oryx/configx/koanf_confmap.go
Normal file
69
oryx/configx/koanf_confmap.go
Normal file
@@ -0,0 +1,69 @@
|
||||
// Copyright © 2023 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package configx
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"github.com/knadh/koanf/maps"
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
// KoanfConfmap implements a raw map[string]interface{} provider.
|
||||
type KoanfConfmap struct {
|
||||
tuples []tuple
|
||||
}
|
||||
|
||||
// Provider returns a confmap Provider that takes a flat or nested
|
||||
// map[string]interface{}. If a delim is provided, it indicates that the
|
||||
// keys are flat and the map needs to be unflatted by delim.
|
||||
func NewKoanfConfmap(tuples []tuple) *KoanfConfmap {
|
||||
return &KoanfConfmap{tuples: jsonify(tuples)}
|
||||
}
|
||||
|
||||
func jsonify(tuples []tuple) []tuple {
|
||||
for k, t := range tuples {
|
||||
var parsed interface{}
|
||||
switch vt := t.Value.(type) {
|
||||
case string:
|
||||
if gjson.Valid(vt) && json.NewDecoder(bytes.NewBufferString(vt)).Decode(&parsed) == nil {
|
||||
tuples[k].Value = parsed
|
||||
}
|
||||
continue
|
||||
case []byte:
|
||||
if gjson.ValidBytes(vt) && json.NewDecoder(bytes.NewBuffer(vt)).Decode(&parsed) == nil {
|
||||
tuples[k].Value = parsed
|
||||
}
|
||||
continue
|
||||
case json.RawMessage:
|
||||
if gjson.ValidBytes(vt) && json.NewDecoder(bytes.NewBuffer(vt)).Decode(&parsed) == nil {
|
||||
tuples[k].Value = parsed
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
return tuples
|
||||
}
|
||||
|
||||
// ReadBytes is not supported by the env provider.
|
||||
func (e *KoanfConfmap) ReadBytes() ([]byte, error) {
|
||||
return nil, errors.New("confmap provider does not support this method")
|
||||
}
|
||||
|
||||
// Read returns the loaded map[string]interface{}.
|
||||
func (e *KoanfConfmap) Read() (map[string]interface{}, error) {
|
||||
values := map[string]interface{}{}
|
||||
for _, t := range e.tuples {
|
||||
values[t.Key] = t.Value
|
||||
}
|
||||
|
||||
// Ensure any nested values are properly converted as well
|
||||
cp := maps.Copy(values)
|
||||
maps.IntfaceKeysToStrings(cp)
|
||||
cp = maps.Unflatten(cp, Delimiter)
|
||||
|
||||
return cp, nil
|
||||
}
|
||||
185
oryx/configx/koanf_env.go
Normal file
185
oryx/configx/koanf_env.go
Normal file
@@ -0,0 +1,185 @@
|
||||
// Copyright © 2023 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package configx
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/tidwall/sjson"
|
||||
|
||||
"github.com/ory/jsonschema/v3"
|
||||
|
||||
"github.com/spf13/cast"
|
||||
"github.com/tidwall/gjson"
|
||||
|
||||
"github.com/ory/x/castx"
|
||||
"github.com/ory/x/jsonschemax"
|
||||
)
|
||||
|
||||
var isNumRegex = regexp.MustCompile("^[0-9]+$")
|
||||
|
||||
func NewKoanfEnv(prefix string, rawSchema []byte, schema *jsonschema.Schema) (*Env, error) {
|
||||
paths, err := getSchemaPaths(rawSchema, schema)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Env{
|
||||
paths: paths,
|
||||
prefix: prefix,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Env implements an environment variables provider.
|
||||
type Env struct {
|
||||
prefix string
|
||||
paths []jsonschemax.Path
|
||||
}
|
||||
|
||||
// ReadBytes is not supported by the env provider.
|
||||
func (e *Env) ReadBytes() ([]byte, error) {
|
||||
return nil, errors.New("env provider does not support this method")
|
||||
}
|
||||
|
||||
// Read reads all available environment variables into a key:value map
|
||||
// and returns it.
|
||||
func (e *Env) Read() (map[string]interface{}, error) {
|
||||
// Collect the environment variable keys.
|
||||
var keys []string
|
||||
for _, k := range os.Environ() {
|
||||
if e.prefix != "" {
|
||||
if strings.HasPrefix(k, e.prefix) {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
} else {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
}
|
||||
|
||||
raw := "{}"
|
||||
var err error
|
||||
for _, k := range keys {
|
||||
parts := strings.SplitN(k, "=", 2)
|
||||
|
||||
key, value := e.extract(parts[0], parts[1])
|
||||
// If the callback blanked the key, it should be omitted
|
||||
if key == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
raw, err = sjson.Set(raw, key, value)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
}
|
||||
|
||||
var m map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(raw), &m); err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// Watch is not supported.
|
||||
func (e *Env) Watch(cb func(event interface{}, err error)) error {
|
||||
return errors.New("env provider does not support this method")
|
||||
}
|
||||
|
||||
func (e *Env) extract(key string, value string) (string, interface{}) {
|
||||
key = strings.Replace(strings.ToLower(strings.TrimPrefix(key, e.prefix)), "_", ".", -1)
|
||||
|
||||
for _, path := range e.paths {
|
||||
normalized := strings.Replace(path.Name, "_", ".", -1)
|
||||
name := path.Name
|
||||
|
||||
// Crazy hack to get arrays working.
|
||||
var indices []string
|
||||
searchParts := strings.Split(normalized, ".")
|
||||
keyParts := strings.Split(key, ".")
|
||||
if len(searchParts) == len(keyParts) {
|
||||
for k, search := range searchParts {
|
||||
if search != keyParts[k] {
|
||||
indices = nil
|
||||
}
|
||||
|
||||
if search != "#" {
|
||||
continue
|
||||
}
|
||||
|
||||
if !isNumRegex.MatchString(keyParts[k]) {
|
||||
continue
|
||||
}
|
||||
|
||||
searchParts[k] = keyParts[k]
|
||||
indices = append(indices, keyParts[k])
|
||||
}
|
||||
}
|
||||
|
||||
if len(indices) > 0 {
|
||||
normalized = strings.Join(searchParts, ".")
|
||||
for _, index := range indices {
|
||||
name = strings.Replace(name, "#", index, 1)
|
||||
}
|
||||
}
|
||||
|
||||
if normalized == key {
|
||||
switch path.TypeHint {
|
||||
case jsonschemax.String:
|
||||
return name, cast.ToString(value)
|
||||
case jsonschemax.Float:
|
||||
return name, cast.ToFloat64(value)
|
||||
case jsonschemax.Int:
|
||||
return name, cast.ToInt64(value)
|
||||
case jsonschemax.Bool:
|
||||
return name, cast.ToBool(value)
|
||||
case jsonschemax.Nil:
|
||||
return name, nil
|
||||
case jsonschemax.BoolSlice:
|
||||
if !gjson.Valid(value) {
|
||||
return name, cast.ToBoolSlice(value)
|
||||
}
|
||||
fallthrough
|
||||
case jsonschemax.StringSlice:
|
||||
if !gjson.Valid(value) {
|
||||
return name, castx.ToStringSlice(value)
|
||||
}
|
||||
fallthrough
|
||||
case jsonschemax.IntSlice:
|
||||
if !gjson.Valid(value) {
|
||||
return name, cast.ToIntSlice(value)
|
||||
}
|
||||
fallthrough
|
||||
case jsonschemax.FloatSlice:
|
||||
if !gjson.Valid(value) {
|
||||
return name, castx.ToFloatSlice(value)
|
||||
}
|
||||
fallthrough
|
||||
case jsonschemax.JSON:
|
||||
return name, decode(value)
|
||||
default:
|
||||
return name, value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func decode(value string) (v interface{}) {
|
||||
b := []byte(value)
|
||||
var arr []interface{}
|
||||
if err := json.Unmarshal(b, &arr); err == nil {
|
||||
return &arr
|
||||
}
|
||||
h := map[string]interface{}{}
|
||||
if err := json.Unmarshal(b, &h); err == nil {
|
||||
return &h
|
||||
}
|
||||
return nil
|
||||
}
|
||||
90
oryx/configx/koanf_file.go
Normal file
90
oryx/configx/koanf_file.go
Normal file
@@ -0,0 +1,90 @@
|
||||
// Copyright © 2023 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package configx
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/knadh/koanf/parsers/json"
|
||||
"github.com/knadh/koanf/parsers/toml"
|
||||
"github.com/knadh/koanf/parsers/yaml"
|
||||
"github.com/knadh/koanf/v2"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/ory/x/watcherx"
|
||||
)
|
||||
|
||||
// KoanfFile implements a KoanfFile provider.
|
||||
type KoanfFile struct {
|
||||
subKey string
|
||||
path string
|
||||
parser koanf.Parser
|
||||
}
|
||||
|
||||
// NewKoanfFile returns a file provider.
|
||||
func NewKoanfFile(path string) (*KoanfFile, error) {
|
||||
return NewKoanfFileSubKey(path, "")
|
||||
}
|
||||
|
||||
func NewKoanfFileSubKey(path, subKey string) (*KoanfFile, error) {
|
||||
kf := &KoanfFile{
|
||||
path: filepath.Clean(path),
|
||||
subKey: subKey,
|
||||
}
|
||||
|
||||
switch e := filepath.Ext(path); e {
|
||||
case ".toml":
|
||||
kf.parser = toml.Parser()
|
||||
case ".json":
|
||||
kf.parser = json.Parser()
|
||||
case ".yaml", ".yml":
|
||||
kf.parser = yaml.Parser()
|
||||
default:
|
||||
return nil, errors.Errorf("unknown config file extension: %s", e)
|
||||
}
|
||||
|
||||
return kf, nil
|
||||
}
|
||||
|
||||
// ReadBytes is not supported by KoanfFile.
|
||||
func (f *KoanfFile) ReadBytes() ([]byte, error) {
|
||||
return nil, errors.New("file provider does not support this method")
|
||||
}
|
||||
|
||||
// Read reads the file and returns the parsed configuration.
|
||||
func (f *KoanfFile) Read() (map[string]interface{}, error) {
|
||||
//#nosec G304 -- false positive
|
||||
fc, err := os.ReadFile(f.path)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
v, err := f.parser.Unmarshal(fc)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
if f.subKey == "" {
|
||||
return v, nil
|
||||
}
|
||||
|
||||
path := strings.Split(f.subKey, Delimiter)
|
||||
for i := range path {
|
||||
v = map[string]interface{}{
|
||||
path[len(path)-1-i]: v,
|
||||
}
|
||||
}
|
||||
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// WatchChannel watches the file and triggers a callback when it changes. It is a
|
||||
// blocking function that internally spawns a goroutine to watch for changes.
|
||||
func (f *KoanfFile) WatchChannel(ctx context.Context, c watcherx.EventChannel) (watcherx.Watcher, error) {
|
||||
return watcherx.WatchFile(ctx, f.path, c)
|
||||
}
|
||||
35
oryx/configx/koanf_full_merge.go
Normal file
35
oryx/configx/koanf_full_merge.go
Normal file
@@ -0,0 +1,35 @@
|
||||
// Copyright © 2023 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package configx
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/tidwall/sjson"
|
||||
|
||||
"github.com/ory/x/jsonx"
|
||||
)
|
||||
|
||||
func MergeAllTypes(src, dst map[string]interface{}) error {
|
||||
rawSrc, err := json.Marshal(src)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
dstSrc, err := json.Marshal(dst)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
keys := jsonx.Flatten(rawSrc)
|
||||
for key, value := range keys {
|
||||
dstSrc, err = sjson.SetBytes(dstSrc, key, value)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
}
|
||||
|
||||
return errors.WithStack(json.Unmarshal(dstSrc, &dst))
|
||||
}
|
||||
46
oryx/configx/koanf_memory.go
Normal file
46
oryx/configx/koanf_memory.go
Normal file
@@ -0,0 +1,46 @@
|
||||
// Copyright © 2023 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package configx
|
||||
|
||||
import (
|
||||
"github.com/knadh/koanf/parsers/json"
|
||||
"github.com/knadh/koanf/v2"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
stdjson "encoding/json"
|
||||
)
|
||||
|
||||
// KoanfMemory implements a KoanfMemory provider.
|
||||
type KoanfMemory struct {
|
||||
doc stdjson.RawMessage
|
||||
parser koanf.Parser
|
||||
}
|
||||
|
||||
// NewKoanfMemory returns a file provider.
|
||||
func NewKoanfMemory(doc stdjson.RawMessage) *KoanfMemory {
|
||||
return &KoanfMemory{
|
||||
doc: doc,
|
||||
parser: json.Parser(),
|
||||
}
|
||||
}
|
||||
|
||||
func (f *KoanfMemory) SetDoc(doc stdjson.RawMessage) {
|
||||
f.doc = doc
|
||||
}
|
||||
|
||||
// ReadBytes reads the contents of a file on disk and returns the bytes.
|
||||
func (f *KoanfMemory) ReadBytes() ([]byte, error) {
|
||||
return nil, errors.New("file provider does not support this method")
|
||||
}
|
||||
|
||||
// Read is not supported by the file provider.
|
||||
func (f *KoanfMemory) Read() (map[string]interface{}, error) {
|
||||
v, err := f.parser.Unmarshal(f.doc)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
return v, nil
|
||||
}
|
||||
53
oryx/configx/koanf_schema_defaults.go
Normal file
53
oryx/configx/koanf_schema_defaults.go
Normal file
@@ -0,0 +1,53 @@
|
||||
// Copyright © 2023 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package configx
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/knadh/koanf/maps"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/ory/jsonschema/v3"
|
||||
"github.com/ory/x/jsonschemax"
|
||||
)
|
||||
|
||||
type KoanfSchemaDefaults struct {
|
||||
keys []jsonschemax.Path
|
||||
}
|
||||
|
||||
func NewKoanfSchemaDefaults(rawSchema []byte, schema *jsonschema.Schema) (*KoanfSchemaDefaults, error) {
|
||||
keys, err := getSchemaPaths(rawSchema, schema)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &KoanfSchemaDefaults{keys: keys}, nil
|
||||
}
|
||||
|
||||
func (k *KoanfSchemaDefaults) ReadBytes() ([]byte, error) {
|
||||
return nil, errors.New("schema defaults provider does not support this method")
|
||||
}
|
||||
|
||||
func (k *KoanfSchemaDefaults) Read() (map[string]interface{}, error) {
|
||||
values := map[string]interface{}{}
|
||||
for _, key := range k.keys {
|
||||
// It's an array!
|
||||
if strings.Contains(key.Name, "#") {
|
||||
continue
|
||||
}
|
||||
|
||||
if key.Default != nil {
|
||||
values[key.Name] = key.Default
|
||||
}
|
||||
}
|
||||
|
||||
// Deep-copy: getSchemaPaths caches Path entries by schema hash, so
|
||||
// key.Default may be a map/slice shared across all callers. koanf.Merge
|
||||
// retains references when merging, and recursively writes into shared
|
||||
// sub-maps when a later provider overlaps the default's path —
|
||||
// without this copy, concurrent configx.New calls race on the shared
|
||||
// default value.
|
||||
return maps.Unflatten(maps.Copy(values), "."), nil
|
||||
}
|
||||
159
oryx/configx/options.go
Normal file
159
oryx/configx/options.go
Normal file
@@ -0,0 +1,159 @@
|
||||
// Copyright © 2023 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package configx
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/pflag"
|
||||
|
||||
"github.com/ory/jsonschema/v3"
|
||||
"github.com/ory/x/logrusx"
|
||||
|
||||
"github.com/knadh/koanf/v2"
|
||||
|
||||
"github.com/ory/x/watcherx"
|
||||
)
|
||||
|
||||
type (
|
||||
OptionModifier func(p *Provider)
|
||||
)
|
||||
|
||||
func WithContext(ctx context.Context) OptionModifier {
|
||||
return func(p *Provider) {
|
||||
for _, o := range ConfigOptionsFromContext(ctx) {
|
||||
o(p)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func WithConfigFiles(files ...string) OptionModifier {
|
||||
return func(p *Provider) {
|
||||
p.files = append(p.files, files...)
|
||||
}
|
||||
}
|
||||
|
||||
func WithImmutables(immutables ...string) OptionModifier {
|
||||
return func(p *Provider) {
|
||||
p.immutables = append(p.immutables, immutables...)
|
||||
}
|
||||
}
|
||||
|
||||
func WithExceptImmutables(exceptImmutables ...string) OptionModifier {
|
||||
return func(p *Provider) {
|
||||
p.exceptImmutables = append(p.exceptImmutables, exceptImmutables...)
|
||||
}
|
||||
}
|
||||
|
||||
func WithFlags(flags *pflag.FlagSet) OptionModifier {
|
||||
return func(p *Provider) {
|
||||
p.flags = flags
|
||||
}
|
||||
}
|
||||
|
||||
func WithLogger(l *logrusx.Logger) OptionModifier {
|
||||
return func(p *Provider) {
|
||||
p.logger = l
|
||||
}
|
||||
}
|
||||
|
||||
func SkipValidation() OptionModifier {
|
||||
return func(p *Provider) {
|
||||
p.skipValidation = true
|
||||
}
|
||||
}
|
||||
|
||||
func DisableEnvLoading() OptionModifier {
|
||||
return func(p *Provider) {
|
||||
p.disableEnvLoading = true
|
||||
}
|
||||
}
|
||||
|
||||
func WithValue(key string, value interface{}) OptionModifier {
|
||||
return func(p *Provider) {
|
||||
p.forcedValues = append(p.forcedValues, tuple{Key: key, Value: value})
|
||||
}
|
||||
}
|
||||
|
||||
func WithValues(values map[string]interface{}) OptionModifier {
|
||||
return func(p *Provider) {
|
||||
for key, value := range values {
|
||||
p.forcedValues = append(p.forcedValues, tuple{Key: key, Value: value})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func WithBaseValues(values map[string]interface{}) OptionModifier {
|
||||
return func(p *Provider) {
|
||||
for key, value := range values {
|
||||
p.baseValues = append(p.baseValues, tuple{Key: key, Value: value})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func WithUserProviders(providers ...koanf.Provider) OptionModifier {
|
||||
return func(p *Provider) {
|
||||
p.userProviders = providers
|
||||
}
|
||||
}
|
||||
|
||||
// DEPRECATED without replacement. This option is a no-op.
|
||||
func OmitKeysFromTracing(keys ...string) OptionModifier {
|
||||
return func(*Provider) {}
|
||||
}
|
||||
|
||||
func AttachWatcher(watcher func(event watcherx.Event, err error)) OptionModifier {
|
||||
return func(p *Provider) {
|
||||
p.onChanges = append(p.onChanges, watcher)
|
||||
}
|
||||
}
|
||||
|
||||
func WithLogrusWatcher(l *logrusx.Logger) OptionModifier {
|
||||
return AttachWatcher(LogrusWatcher(l))
|
||||
}
|
||||
|
||||
func LogrusWatcher(l *logrusx.Logger) func(e watcherx.Event, err error) {
|
||||
return func(e watcherx.Event, err error) {
|
||||
l.WithField("file", e.Source()).
|
||||
WithField("event_type", fmt.Sprintf("%T", e)).
|
||||
Info("A change to a configuration file was detected.")
|
||||
|
||||
if et := new(jsonschema.ValidationError); errors.As(err, &et) {
|
||||
l.WithField("event", fmt.Sprintf("%#v", et)).
|
||||
Errorf("The changed configuration is invalid and could not be loaded. Rolling back to the last working configuration revision. Please address the validation errors before restarting the process.")
|
||||
} else if et := new(ImmutableError); errors.As(err, &et) {
|
||||
l.WithError(err).
|
||||
WithField("key", et.Key).
|
||||
WithField("old_value", fmt.Sprintf("%v", et.From)).
|
||||
WithField("new_value", fmt.Sprintf("%v", et.To)).
|
||||
Errorf("A configuration value marked as immutable has changed. Rolling back to the last working configuration revision. To reload the values please restart the process.")
|
||||
} else if err != nil {
|
||||
l.WithError(err).Errorf("An error occurred while watching config file %s", e.Source())
|
||||
} else {
|
||||
l.WithField("file", e.Source()).
|
||||
WithField("event_type", fmt.Sprintf("%T", e)).
|
||||
Info("Configuration change processed successfully.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func WithStderrValidationReporter() OptionModifier {
|
||||
return func(p *Provider) {
|
||||
p.onValidationError = func(k *koanf.Koanf, err error) {
|
||||
p.printHumanReadableValidationErrors(k, os.Stderr, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func WithStandardValidationReporter(w io.Writer) OptionModifier {
|
||||
return func(p *Provider) {
|
||||
p.onValidationError = func(k *koanf.Koanf, err error) {
|
||||
p.printHumanReadableValidationErrors(k, w, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
56
oryx/configx/permission.go
Normal file
56
oryx/configx/permission.go
Normal file
@@ -0,0 +1,56 @@
|
||||
// Copyright © 2023 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package configx
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/user"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type UnixPermission struct {
|
||||
Owner string
|
||||
Group string
|
||||
Mode os.FileMode
|
||||
}
|
||||
|
||||
func (p *UnixPermission) SetPermission(file string) error {
|
||||
var e error
|
||||
e = os.Chmod(file, p.Mode)
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
|
||||
gid := -1
|
||||
uid := -1
|
||||
|
||||
if p.Owner != "" {
|
||||
var userObj *user.User
|
||||
userObj, e = user.Lookup(p.Owner)
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
uid, e = strconv.Atoi(userObj.Uid)
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
}
|
||||
if p.Group != "" {
|
||||
var group *user.Group
|
||||
group, e := user.LookupGroup(p.Group)
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
gid, e = strconv.Atoi(group.Gid)
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
}
|
||||
|
||||
e = os.Chown(file, uid, gid)
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
return nil
|
||||
}
|
||||
57
oryx/configx/pflag.go
Normal file
57
oryx/configx/pflag.go
Normal file
@@ -0,0 +1,57 @@
|
||||
// Copyright © 2023 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package configx
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/knadh/koanf/providers/posflag"
|
||||
"github.com/knadh/koanf/v2"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/pflag"
|
||||
|
||||
"github.com/ory/jsonschema/v3"
|
||||
"github.com/ory/x/jsonschemax"
|
||||
)
|
||||
|
||||
type PFlagProvider struct {
|
||||
p *posflag.Posflag
|
||||
paths []jsonschemax.Path
|
||||
}
|
||||
|
||||
func NewPFlagProvider(rawSchema []byte, schema *jsonschema.Schema, f *pflag.FlagSet, k *koanf.Koanf) (*PFlagProvider, error) {
|
||||
paths, err := getSchemaPaths(rawSchema, schema)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &PFlagProvider{
|
||||
p: posflag.Provider(f, ".", k),
|
||||
paths: paths,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *PFlagProvider) ReadBytes() ([]byte, error) {
|
||||
return nil, errors.New("pflag provider does not support this method")
|
||||
}
|
||||
|
||||
func (p *PFlagProvider) Read() (map[string]interface{}, error) {
|
||||
all, err := p.p.Read()
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
knownFlags := make(map[string]interface{}, len(all))
|
||||
for k, v := range all {
|
||||
k = strings.ReplaceAll(k, ".", "-")
|
||||
for _, path := range p.paths {
|
||||
normalized := strings.ReplaceAll(path.Name, ".", "-")
|
||||
if k == normalized {
|
||||
knownFlags[k] = v
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return knownFlags, nil
|
||||
}
|
||||
|
||||
var _ koanf.Provider = (*PFlagProvider)(nil)
|
||||
561
oryx/configx/provider.go
Normal file
561
oryx/configx/provider.go
Normal file
@@ -0,0 +1,561 @@
|
||||
// Copyright © 2023 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package configx
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
"os"
|
||||
"reflect"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/inhies/go-bytesize"
|
||||
"github.com/knadh/koanf/parsers/json"
|
||||
"github.com/knadh/koanf/providers/posflag"
|
||||
"github.com/knadh/koanf/v2"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/pflag"
|
||||
|
||||
"github.com/ory/jsonschema/v3"
|
||||
"github.com/ory/x/jsonschemax"
|
||||
"github.com/ory/x/logrusx"
|
||||
"github.com/ory/x/otelx"
|
||||
"github.com/ory/x/watcherx"
|
||||
)
|
||||
|
||||
type tuple struct {
|
||||
Key string
|
||||
Value interface{}
|
||||
}
|
||||
|
||||
type Provider struct {
|
||||
l sync.RWMutex
|
||||
*koanf.Koanf
|
||||
immutables, exceptImmutables []string
|
||||
|
||||
schema []byte
|
||||
flags *pflag.FlagSet
|
||||
validator *jsonschema.Schema
|
||||
onChanges []func(watcherx.Event, error)
|
||||
onValidationError func(k *koanf.Koanf, err error)
|
||||
|
||||
forcedValues []tuple
|
||||
baseValues []tuple
|
||||
files []string
|
||||
|
||||
skipValidation bool
|
||||
disableEnvLoading bool
|
||||
|
||||
logger *logrusx.Logger
|
||||
|
||||
providers []koanf.Provider
|
||||
userProviders []koanf.Provider
|
||||
}
|
||||
|
||||
const (
|
||||
FlagConfig = "config"
|
||||
Delimiter = "."
|
||||
)
|
||||
|
||||
// RegisterConfigFlag registers the "--config" flag on pflag.FlagSet.
|
||||
func RegisterConfigFlag(flags *pflag.FlagSet, fallback []string) {
|
||||
flags.StringSliceP(FlagConfig, "c", fallback, "Config files to load, overwriting in the order specified.")
|
||||
}
|
||||
|
||||
// New creates a new provider instance or errors.
|
||||
// Configuration values are loaded in the following order:
|
||||
//
|
||||
// 1. Defaults from the JSON Schema
|
||||
// 2. Config files (yaml, yml, toml, json)
|
||||
// 3. Command line flags
|
||||
// 4. Environment variables
|
||||
//
|
||||
// There will also be file-watchers started for all config files. To cancel the
|
||||
// watchers, cancel the context.
|
||||
func New(ctx context.Context, schema []byte, modifiers ...OptionModifier) (*Provider, error) {
|
||||
validator, err := getSchema(ctx, schema)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
l := logrus.New()
|
||||
l.Out = io.Discard
|
||||
|
||||
p := &Provider{
|
||||
schema: schema,
|
||||
validator: validator,
|
||||
onValidationError: func(k *koanf.Koanf, err error) {},
|
||||
logger: logrusx.New("discarding config logger", "", logrusx.UseLogger(l)),
|
||||
Koanf: koanf.NewWithConf(koanf.Conf{Delim: Delimiter, StrictMerge: true}),
|
||||
}
|
||||
|
||||
for _, m := range modifiers {
|
||||
m(p)
|
||||
}
|
||||
|
||||
providers, err := p.createProviders(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
p.providers = providers
|
||||
|
||||
k, err := p.newKoanf()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
p.replaceKoanf(k)
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func (p *Provider) SkipValidation() bool {
|
||||
return p.skipValidation
|
||||
}
|
||||
|
||||
func (p *Provider) createProviders(ctx context.Context) (providers []koanf.Provider, err error) {
|
||||
defaultsProvider, err := NewKoanfSchemaDefaults(p.schema, p.validator)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
providers = append(providers, defaultsProvider)
|
||||
|
||||
// Workaround for https://github.com/knadh/koanf/pull/47
|
||||
for _, t := range p.baseValues {
|
||||
providers = append(providers, NewKoanfConfmap([]tuple{t}))
|
||||
}
|
||||
|
||||
paths := p.files
|
||||
if p.flags != nil {
|
||||
p, _ := p.flags.GetStringSlice(FlagConfig)
|
||||
paths = append(paths, p...)
|
||||
}
|
||||
|
||||
p.logger.WithField("files", paths).Debug("Adding config files.")
|
||||
|
||||
c := make(watcherx.EventChannel)
|
||||
|
||||
defer func() {
|
||||
if err == nil && len(paths) > 0 {
|
||||
go p.watchForFileChanges(ctx, c)
|
||||
}
|
||||
}()
|
||||
for _, path := range paths {
|
||||
fp, err := NewKoanfFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if _, err := fp.WatchChannel(ctx, c); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
providers = append(providers, fp)
|
||||
}
|
||||
|
||||
providers = append(providers, p.userProviders...)
|
||||
|
||||
if p.flags != nil {
|
||||
pp, err := NewPFlagProvider(p.schema, p.validator, p.flags, p.Koanf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
providers = append(providers, pp)
|
||||
}
|
||||
|
||||
if !p.disableEnvLoading {
|
||||
envProvider, err := NewKoanfEnv("", p.schema, p.validator)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
providers = append(providers, envProvider)
|
||||
}
|
||||
|
||||
// Workaround for https://github.com/knadh/koanf/pull/47
|
||||
for _, t := range p.forcedValues {
|
||||
providers = append(providers, NewKoanfConfmap([]tuple{t}))
|
||||
}
|
||||
|
||||
return providers, nil
|
||||
}
|
||||
|
||||
func (p *Provider) replaceKoanf(k *koanf.Koanf) {
|
||||
p.Koanf = k
|
||||
}
|
||||
|
||||
func (p *Provider) validate(k *koanf.Koanf) error {
|
||||
if p.skipValidation {
|
||||
return nil
|
||||
}
|
||||
|
||||
out, err := k.Marshal(json.Parser())
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
if err := p.validator.Validate(bytes.NewReader(out)); err != nil {
|
||||
p.onValidationError(k, err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// newKoanf creates a new koanf instance with all the updated config
|
||||
//
|
||||
// This is unfortunately required due to several limitations / bugs in koanf:
|
||||
//
|
||||
// - https://github.com/knadh/koanf/issues/77
|
||||
// - https://github.com/knadh/koanf/pull/47
|
||||
func (p *Provider) newKoanf() (_ *koanf.Koanf, err error) {
|
||||
k := koanf.New(Delimiter)
|
||||
|
||||
for _, provider := range p.providers {
|
||||
// posflag.Posflag requires access to Koanf instance so we recreate the provider here which is a workaround
|
||||
// for posflag.Provider's API.
|
||||
if _, ok := provider.(*posflag.Posflag); ok {
|
||||
provider = posflag.Provider(p.flags, ".", k)
|
||||
}
|
||||
|
||||
var opts []koanf.Option
|
||||
if _, ok := provider.(*Env); ok {
|
||||
opts = append(opts, koanf.WithMergeFunc(MergeAllTypes))
|
||||
}
|
||||
|
||||
if err := k.Load(provider, nil, opts...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if err := p.validate(k); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return k, nil
|
||||
}
|
||||
|
||||
// SetTracer does nothing. DEPRECATED without replacement.
|
||||
func (p *Provider) SetTracer(_ context.Context, _ *otelx.Tracer) {
|
||||
}
|
||||
|
||||
func (p *Provider) runOnChanges(e watcherx.Event, err error) {
|
||||
for k := range p.onChanges {
|
||||
p.onChanges[k](e, err)
|
||||
}
|
||||
}
|
||||
|
||||
func deleteOtherKeys(k *koanf.Koanf, keys []string) {
|
||||
outer:
|
||||
for _, key := range k.Keys() {
|
||||
for _, ik := range keys {
|
||||
if key == ik {
|
||||
continue outer
|
||||
}
|
||||
}
|
||||
k.Delete(key)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Provider) reload(e watcherx.Event) {
|
||||
p.l.Lock()
|
||||
|
||||
var err error
|
||||
defer func() {
|
||||
// we first want to unlock and then runOnChanges, so that the callbacks can actually use the Provider
|
||||
p.l.Unlock()
|
||||
p.runOnChanges(e, err)
|
||||
}()
|
||||
|
||||
nk, err := p.newKoanf()
|
||||
if err != nil {
|
||||
return // unlocks & runs changes in defer
|
||||
}
|
||||
|
||||
oldImmutables, newImmutables := p.Koanf.Copy(), nk.Copy()
|
||||
deleteOtherKeys(oldImmutables, p.immutables)
|
||||
deleteOtherKeys(newImmutables, p.immutables)
|
||||
|
||||
for _, key := range p.exceptImmutables {
|
||||
oldImmutables.Delete(key)
|
||||
newImmutables.Delete(key)
|
||||
}
|
||||
if !reflect.DeepEqual(oldImmutables.Raw(), newImmutables.Raw()) {
|
||||
for _, key := range p.immutables {
|
||||
if !reflect.DeepEqual(oldImmutables.Get(key), newImmutables.Get(key)) {
|
||||
err = NewImmutableError(key, fmt.Sprintf("%v", p.Koanf.Get(key)), fmt.Sprintf("%v", nk.Get(key)))
|
||||
return // unlocks & runs changes in defer
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
p.replaceKoanf(nk)
|
||||
|
||||
// unlocks & runs changes in defer
|
||||
}
|
||||
|
||||
func (p *Provider) watchForFileChanges(ctx context.Context, c watcherx.EventChannel) {
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case e := <-c:
|
||||
switch et := e.(type) {
|
||||
case *watcherx.ErrorEvent:
|
||||
p.runOnChanges(e, et)
|
||||
default:
|
||||
p.reload(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DirtyPatch patches individual config keys without reloading the full config
|
||||
//
|
||||
// WARNING! This method is only useful to override existing keys in string or number
|
||||
// format. DO NOT use this method to override arrays, maps, or other complex types.
|
||||
//
|
||||
// This method DOES NOT validate the config against the config JSON schema. If you
|
||||
// need to validate the config, use the Set method instead.
|
||||
//
|
||||
// This method can not be used to remove keys from the config as that is not
|
||||
// possible without reloading the full config.
|
||||
func (p *Provider) DirtyPatch(key string, value any) error {
|
||||
p.l.Lock()
|
||||
defer p.l.Unlock()
|
||||
|
||||
t := tuple{Key: key, Value: value}
|
||||
kc := NewKoanfConfmap([]tuple{t})
|
||||
|
||||
p.forcedValues = append(p.forcedValues, t)
|
||||
p.providers = append(p.providers, kc)
|
||||
|
||||
if err := p.Koanf.Load(kc, nil, []koanf.Option{}...); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Provider) Set(key string, value interface{}) error {
|
||||
p.l.Lock()
|
||||
defer p.l.Unlock()
|
||||
|
||||
p.forcedValues = append(p.forcedValues, tuple{Key: key, Value: value})
|
||||
p.providers = append(p.providers, NewKoanfConfmap([]tuple{{Key: key, Value: value}}))
|
||||
|
||||
k, err := p.newKoanf()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p.replaceKoanf(k)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ConfigFiles returns all config file paths configured on this provider,
|
||||
// including paths set via WithConfigFiles and via the --config flag.
|
||||
func (p *Provider) ConfigFiles() []string {
|
||||
paths := make([]string, len(p.files))
|
||||
copy(paths, p.files)
|
||||
if p.flags != nil {
|
||||
flagPaths, _ := p.flags.GetStringSlice(FlagConfig)
|
||||
paths = append(paths, flagPaths...)
|
||||
}
|
||||
return paths
|
||||
}
|
||||
|
||||
func (p *Provider) BoolF(key string, fallback bool) bool {
|
||||
p.l.RLock()
|
||||
defer p.l.RUnlock()
|
||||
|
||||
if !p.Koanf.Exists(key) {
|
||||
return fallback
|
||||
}
|
||||
|
||||
return p.Bool(key)
|
||||
}
|
||||
|
||||
func (p *Provider) StringF(key string, fallback string) string {
|
||||
p.l.RLock()
|
||||
defer p.l.RUnlock()
|
||||
|
||||
if !p.Koanf.Exists(key) {
|
||||
return fallback
|
||||
}
|
||||
|
||||
return p.String(key)
|
||||
}
|
||||
|
||||
func (p *Provider) StringsF(key string, fallback []string) (val []string) {
|
||||
p.l.RLock()
|
||||
defer p.l.RUnlock()
|
||||
|
||||
if !p.Koanf.Exists(key) {
|
||||
return fallback
|
||||
}
|
||||
|
||||
return p.Strings(key)
|
||||
}
|
||||
|
||||
func (p *Provider) IntF(key string, fallback int) (val int) {
|
||||
p.l.RLock()
|
||||
defer p.l.RUnlock()
|
||||
|
||||
if !p.Koanf.Exists(key) {
|
||||
return fallback
|
||||
}
|
||||
|
||||
return p.Int(key)
|
||||
}
|
||||
|
||||
func (p *Provider) Float64F(key string, fallback float64) (val float64) {
|
||||
p.l.RLock()
|
||||
defer p.l.RUnlock()
|
||||
|
||||
if !p.Koanf.Exists(key) {
|
||||
return fallback
|
||||
}
|
||||
|
||||
return p.Float64(key)
|
||||
}
|
||||
|
||||
func (p *Provider) DurationF(key string, fallback time.Duration) (val time.Duration) {
|
||||
p.l.RLock()
|
||||
defer p.l.RUnlock()
|
||||
|
||||
if !p.Koanf.Exists(key) {
|
||||
return fallback
|
||||
}
|
||||
|
||||
return p.Duration(key)
|
||||
}
|
||||
|
||||
func (p *Provider) ByteSizeF(key string, fallback bytesize.ByteSize) bytesize.ByteSize {
|
||||
p.l.RLock()
|
||||
defer p.l.RUnlock()
|
||||
|
||||
if !p.Koanf.Exists(key) {
|
||||
return fallback
|
||||
}
|
||||
|
||||
switch v := p.Koanf.Get(key).(type) {
|
||||
case string:
|
||||
// this type usually comes from user input
|
||||
dec, err := bytesize.Parse(v)
|
||||
if err != nil {
|
||||
p.logger.WithField("key", key).WithField("raw_value", v).WithError(err).Warnf("error parsing byte size value, using fallback of %s", fallback)
|
||||
return fallback
|
||||
}
|
||||
return dec
|
||||
case float64:
|
||||
// this type comes from json.Unmarshal
|
||||
return bytesize.ByteSize(v)
|
||||
case bytesize.ByteSize:
|
||||
return v
|
||||
default:
|
||||
p.logger.WithField("key", key).WithField("raw_type", fmt.Sprintf("%T", v)).WithField("raw_value", fmt.Sprintf("%+v", v)).Errorf("error converting byte size value because of unknown type, using fallback of %s", fallback)
|
||||
return fallback
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Provider) GetF(key string, fallback interface{}) (val interface{}) {
|
||||
p.l.RLock()
|
||||
defer p.l.RUnlock()
|
||||
|
||||
if !p.Exists(key) {
|
||||
return fallback
|
||||
}
|
||||
|
||||
return p.Get(key)
|
||||
}
|
||||
|
||||
func (p *Provider) TracingConfig(serviceName string) *otelx.Config {
|
||||
return &otelx.Config{
|
||||
ServiceName: p.StringF("tracing.service_name", serviceName),
|
||||
DeploymentEnvironment: p.StringF("tracing.deployment_environment", ""),
|
||||
Provider: p.String("tracing.provider"),
|
||||
Providers: otelx.ProvidersConfig{
|
||||
Jaeger: otelx.JaegerConfig{
|
||||
Sampling: otelx.JaegerSampling{
|
||||
ServerURL: p.String("tracing.providers.jaeger.sampling.server_url"),
|
||||
TraceIDRatio: p.Float64F("tracing.providers.jaeger.sampling.trace_id_ratio", 1),
|
||||
},
|
||||
LocalAgentAddress: p.String("tracing.providers.jaeger.local_agent_address"),
|
||||
},
|
||||
Zipkin: otelx.ZipkinConfig{
|
||||
ServerURL: p.String("tracing.providers.zipkin.server_url"),
|
||||
Sampling: otelx.ZipkinSampling{
|
||||
SamplingRatio: p.Float64("tracing.providers.zipkin.sampling.sampling_ratio"),
|
||||
},
|
||||
},
|
||||
OTLP: otelx.OTLPConfig{
|
||||
ServerURL: p.String("tracing.providers.otlp.server_url"),
|
||||
Insecure: p.Bool("tracing.providers.otlp.insecure"),
|
||||
Sampling: otelx.OTLPSampling{
|
||||
SamplingRatio: p.Float64F("tracing.providers.otlp.sampling.sampling_ratio", 1),
|
||||
},
|
||||
AuthorizationHeader: p.String("tracing.providers.otlp.authorization_header"),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Provider) RequestURIF(path string, fallback *url.URL) *url.URL {
|
||||
p.l.RLock()
|
||||
defer p.l.RUnlock()
|
||||
|
||||
switch t := p.Get(path).(type) {
|
||||
case *url.URL:
|
||||
return t
|
||||
case url.URL:
|
||||
return &t
|
||||
case string:
|
||||
if parsed, err := url.ParseRequestURI(t); err == nil {
|
||||
return parsed
|
||||
}
|
||||
}
|
||||
|
||||
return fallback
|
||||
}
|
||||
|
||||
func (p *Provider) URIF(path string, fallback *url.URL) *url.URL {
|
||||
p.l.RLock()
|
||||
defer p.l.RUnlock()
|
||||
|
||||
switch t := p.Get(path).(type) {
|
||||
case *url.URL:
|
||||
return t
|
||||
case url.URL:
|
||||
return &t
|
||||
case string:
|
||||
if parsed, err := url.Parse(t); err == nil {
|
||||
return parsed
|
||||
}
|
||||
}
|
||||
|
||||
return fallback
|
||||
}
|
||||
|
||||
// PrintHumanReadableValidationErrors prints human readable validation errors. Duh.
|
||||
func (p *Provider) PrintHumanReadableValidationErrors(w io.Writer, err error) {
|
||||
p.printHumanReadableValidationErrors(p.Koanf, w, err)
|
||||
}
|
||||
|
||||
func (p *Provider) printHumanReadableValidationErrors(k *koanf.Koanf, w io.Writer, err error) {
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
|
||||
_, _ = fmt.Fprintln(os.Stderr, "")
|
||||
conf, innerErr := k.Marshal(json.Parser())
|
||||
if innerErr != nil {
|
||||
_, _ = fmt.Fprintf(w, "Unable to unmarshal configuration: %+v", innerErr)
|
||||
}
|
||||
|
||||
jsonschemax.FormatValidationErrorForCLI(w, conf, err)
|
||||
}
|
||||
45
oryx/configx/schema.go
Normal file
45
oryx/configx/schema.go
Normal file
@@ -0,0 +1,45 @@
|
||||
// Copyright © 2023 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package configx
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
|
||||
"github.com/ory/x/logrusx"
|
||||
"github.com/ory/x/otelx"
|
||||
|
||||
"github.com/gofrs/uuid"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/tidwall/gjson"
|
||||
|
||||
"github.com/ory/jsonschema/v3"
|
||||
)
|
||||
|
||||
func newCompiler(schema []byte) (string, *jsonschema.Compiler, error) {
|
||||
id := gjson.GetBytes(schema, "$id").String()
|
||||
if id == "" {
|
||||
id = fmt.Sprintf("%s.json", uuid.Must(uuid.NewV4()).String())
|
||||
}
|
||||
|
||||
compiler := jsonschema.NewCompiler()
|
||||
if err := compiler.AddResource(id, bytes.NewReader(schema)); err != nil {
|
||||
return "", nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
// DO NOT REMOVE THIS
|
||||
compiler.ExtractAnnotations = true
|
||||
|
||||
if err := otelx.AddConfigSchema(compiler); err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
if err := logrusx.AddConfigSchema(compiler); err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
if err := AddSchemaResources(compiler); err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
return id, compiler, nil
|
||||
}
|
||||
48
oryx/configx/schema_cache.go
Normal file
48
oryx/configx/schema_cache.go
Normal file
@@ -0,0 +1,48 @@
|
||||
// Copyright © 2023 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package configx
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
|
||||
"github.com/dgraph-io/ristretto/v2"
|
||||
|
||||
"github.com/ory/jsonschema/v3"
|
||||
)
|
||||
|
||||
var schemaCacheConfig = &ristretto.Config[[]byte, *jsonschema.Schema]{
|
||||
// Hold up to 25 schemas in cache. Usually we only need one.
|
||||
MaxCost: 25,
|
||||
NumCounters: 250,
|
||||
BufferItems: 64,
|
||||
Metrics: false,
|
||||
IgnoreInternalCost: true,
|
||||
Cost: func(*jsonschema.Schema) int64 {
|
||||
return 1
|
||||
},
|
||||
}
|
||||
|
||||
var schemaCache, _ = ristretto.NewCache(schemaCacheConfig)
|
||||
|
||||
func getSchema(ctx context.Context, schema []byte) (*jsonschema.Schema, error) {
|
||||
key := sha256.Sum256(schema)
|
||||
if val, found := schemaCache.Get(key[:]); found {
|
||||
return val, nil
|
||||
}
|
||||
|
||||
schemaID, comp, err := newCompiler(schema)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
validator, err := comp.Compile(ctx, schemaID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
schemaCache.Set(key[:], validator, 1)
|
||||
schemaCache.Wait()
|
||||
return validator, nil
|
||||
}
|
||||
41
oryx/configx/schema_path_cache.go
Normal file
41
oryx/configx/schema_path_cache.go
Normal file
@@ -0,0 +1,41 @@
|
||||
// Copyright © 2023 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package configx
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
|
||||
"github.com/ory/x/jsonschemax"
|
||||
|
||||
"github.com/dgraph-io/ristretto/v2"
|
||||
|
||||
"github.com/ory/jsonschema/v3"
|
||||
)
|
||||
|
||||
var schemaPathCacheConfig = &ristretto.Config[[]byte, []jsonschemax.Path]{
|
||||
// Hold up to 25 schemas in cache. Usually we only need one.
|
||||
MaxCost: 250,
|
||||
NumCounters: 2500,
|
||||
BufferItems: 64,
|
||||
Metrics: false,
|
||||
IgnoreInternalCost: true,
|
||||
}
|
||||
|
||||
var schemaPathCache, _ = ristretto.NewCache(schemaPathCacheConfig)
|
||||
|
||||
func getSchemaPaths(rawSchema []byte, schema *jsonschema.Schema) ([]jsonschemax.Path, error) {
|
||||
key := sha256.Sum256(rawSchema)
|
||||
if val, found := schemaPathCache.Get(key[:]); found {
|
||||
return val, nil
|
||||
}
|
||||
|
||||
keys, err := jsonschemax.ListPathsWithInitializedSchemaAndArraysIncluded(schema)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
schemaPathCache.Set(key[:], keys, 1)
|
||||
schemaPathCache.Wait()
|
||||
return keys, nil
|
||||
}
|
||||
137
oryx/configx/serve.go
Normal file
137
oryx/configx/serve.go
Normal file
@@ -0,0 +1,137 @@
|
||||
// Copyright © 2025 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package configx
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
|
||||
"github.com/ory/x/logrusx"
|
||||
"github.com/ory/x/tlsx"
|
||||
)
|
||||
|
||||
const (
|
||||
ServeConfigSchemaID = "ory://serve-config"
|
||||
TLSConfigSchemaID = "ory://tls-config"
|
||||
)
|
||||
|
||||
//go:embed serve.schema.json
|
||||
var ServeConfigSchema []byte
|
||||
|
||||
//go:embed tls.schema.json
|
||||
var TLSConfigSchema []byte
|
||||
|
||||
type (
|
||||
Serve struct {
|
||||
Host, WriteListenFile string
|
||||
Port int
|
||||
BaseURL *url.URL
|
||||
Socket UnixPermission
|
||||
TLS TLS
|
||||
RequestLog ServeRequestLog
|
||||
}
|
||||
TLS struct {
|
||||
Enabled bool
|
||||
AllowTerminationFrom []string
|
||||
CertBase64, KeyBase64, CertPath, KeyPath string
|
||||
}
|
||||
ServeRequestLog struct {
|
||||
DisableHealth bool
|
||||
}
|
||||
)
|
||||
|
||||
func (p *Provider) Serve(prefix string, isDev bool, defaults Serve) *Serve {
|
||||
prefix = cleanPrefix(prefix)
|
||||
|
||||
defaults.Socket.Mode = cmp.Or(defaults.Socket.Mode, 0o755)
|
||||
|
||||
serve := Serve{
|
||||
Host: p.StringF(prefix+"host", defaults.Host),
|
||||
Port: p.IntF(prefix+"port", defaults.Port),
|
||||
WriteListenFile: p.StringF(prefix+"write_listen_file", defaults.WriteListenFile),
|
||||
BaseURL: p.URIF(prefix+"base_url", defaults.BaseURL),
|
||||
Socket: UnixPermission{
|
||||
Owner: p.StringF(prefix+"socket.owner", defaults.Socket.Owner),
|
||||
Group: p.StringF(prefix+"socket.group", defaults.Socket.Group),
|
||||
Mode: os.FileMode(p.IntF(prefix+"socket.mode", int(defaults.Socket.Mode))),
|
||||
},
|
||||
TLS: p.TLS(prefix+"tls", defaults.TLS),
|
||||
RequestLog: ServeRequestLog{
|
||||
DisableHealth: p.BoolF(prefix+"request_log.disable_for_health", defaults.RequestLog.DisableHealth),
|
||||
},
|
||||
}
|
||||
|
||||
if serve.BaseURL == nil {
|
||||
serve.BaseURL = &url.URL{
|
||||
Scheme: "http",
|
||||
Path: "/",
|
||||
}
|
||||
if !isDev || serve.TLS.Enabled {
|
||||
serve.BaseURL.Scheme = "https"
|
||||
}
|
||||
host := serve.Host
|
||||
if host == "0.0.0.0" || host == "" {
|
||||
var err error
|
||||
host, err = os.Hostname()
|
||||
if err != nil {
|
||||
p.logger.WithError(err).Warn("Unable to get hostname from system, falling back to 127.0.0.1.")
|
||||
host = "127.0.0.1"
|
||||
}
|
||||
}
|
||||
serve.BaseURL.Host = fmt.Sprintf("%s:%d", host, serve.Port)
|
||||
}
|
||||
|
||||
return &serve
|
||||
}
|
||||
|
||||
func (p *Provider) TLS(prefix string, defaults TLS) TLS {
|
||||
prefix = cleanPrefix(prefix)
|
||||
|
||||
return TLS{
|
||||
Enabled: p.BoolF(prefix+"enabled", defaults.Enabled),
|
||||
AllowTerminationFrom: p.StringsF(prefix+"allow_termination_from", defaults.AllowTerminationFrom),
|
||||
CertBase64: p.StringF(prefix+"cert.base64", defaults.CertBase64),
|
||||
KeyBase64: p.StringF(prefix+"key.base64", defaults.KeyBase64),
|
||||
CertPath: p.StringF(prefix+"cert.path", defaults.CertPath),
|
||||
KeyPath: p.StringF(prefix+"key.path", defaults.KeyPath),
|
||||
}
|
||||
}
|
||||
|
||||
func (t *TLS) GetCertFunc(ctx context.Context, l *logrusx.Logger, ifaceName string) (tlsx.CertFunc, error) {
|
||||
switch {
|
||||
case t.CertBase64 != "" && t.KeyBase64 != "":
|
||||
cert, err := tlsx.CertificateFromBase64(t.CertBase64, t.KeyBase64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to load TLS certificate for interface %s: %w", ifaceName, err)
|
||||
}
|
||||
l.Infof("Setting up HTTPS for %s", ifaceName)
|
||||
return func(*tls.ClientHelloInfo) (*tls.Certificate, error) { return &cert, nil }, nil
|
||||
case t.CertPath != "" && t.KeyPath != "":
|
||||
errs := make(chan error, 1)
|
||||
getCert, err := tlsx.GetCertificate(ctx, t.CertPath, t.KeyPath, errs)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to load TLS certificate for interface %s: %w", ifaceName, err)
|
||||
}
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case err := <-errs:
|
||||
l.WithError(err).Error("Failed to reload TLS certificates, using previous certificates")
|
||||
}
|
||||
}
|
||||
}()
|
||||
l.Infof("Setting up HTTPS for %s (automatic certificate reloading active)", ifaceName)
|
||||
return getCert, nil
|
||||
default:
|
||||
l.Infof("TLS has not been configured for %s, skipping", ifaceName)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
70
oryx/configx/serve.schema.json
Normal file
70
oryx/configx/serve.schema.json
Normal file
@@ -0,0 +1,70 @@
|
||||
{
|
||||
"$id": "https://github.com/ory/x/configx/serve.schema.json",
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"request_log": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"disable_for_health": {
|
||||
"title": "Disable health endpoints request logging",
|
||||
"description": "Disable request logging for /health/alive and /health/ready endpoints",
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"base_url": {
|
||||
"title": "Base URL",
|
||||
"description": "The URL where the endpoint is exposed at. This domain is used to generate redirects, form URLs, and more.",
|
||||
"type": "string",
|
||||
"format": "uri-reference",
|
||||
"examples": [
|
||||
"https://my-app.com/",
|
||||
"https://my-app.com/.ory/kratos/public",
|
||||
"https://auth.my-app.com/hydra"
|
||||
]
|
||||
},
|
||||
"host": {
|
||||
"title": "Host",
|
||||
"description": "The host (interface) that the endpoint listens on.",
|
||||
"type": "string",
|
||||
"default": "0.0.0.0"
|
||||
},
|
||||
"port": {
|
||||
"title": "Port",
|
||||
"description": "The port that the endpoint listens on.",
|
||||
"type": "integer",
|
||||
"minimum": 1,
|
||||
"maximum": 65535
|
||||
},
|
||||
"socket": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"description": "Sets the permissions of the unix socket",
|
||||
"properties": {
|
||||
"owner": {
|
||||
"type": "string",
|
||||
"description": "Owner of unix socket. If empty, the owner will be the user running the service.",
|
||||
"default": ""
|
||||
},
|
||||
"group": {
|
||||
"type": "string",
|
||||
"description": "Group of unix socket. If empty, the group will be the primary group of the user running the service.",
|
||||
"default": ""
|
||||
},
|
||||
"mode": {
|
||||
"type": "integer",
|
||||
"description": "Mode of unix socket in numeric form",
|
||||
"default": 493,
|
||||
"minimum": 0,
|
||||
"maximum": 511
|
||||
}
|
||||
}
|
||||
},
|
||||
"tls": {
|
||||
"$ref": "ory://tls-config"
|
||||
}
|
||||
}
|
||||
}
|
||||
10
oryx/configx/span.go
Normal file
10
oryx/configx/span.go
Normal file
@@ -0,0 +1,10 @@
|
||||
// Copyright © 2023 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package configx
|
||||
|
||||
const (
|
||||
LoadSpanOpName = "config-load"
|
||||
UpdatedSpanOpName = "config-update"
|
||||
SnapshotSpanOpName = "config-snapshot"
|
||||
)
|
||||
312
oryx/configx/stub/benchmark/benchmark.yaml
Normal file
312
oryx/configx/stub/benchmark/benchmark.yaml
Normal file
@@ -0,0 +1,312 @@
|
||||
# Please find the documentation for this file at
|
||||
# https://www.ory.com/oathkeeper/docs/configuration
|
||||
|
||||
log:
|
||||
level: debug
|
||||
format: json
|
||||
|
||||
profiling: cpu
|
||||
|
||||
serve:
|
||||
proxy:
|
||||
port: 1234
|
||||
host: 127.0.0.1
|
||||
|
||||
timeout:
|
||||
read: 1s
|
||||
write: 2s
|
||||
idle: 3s
|
||||
|
||||
cors:
|
||||
enabled: true
|
||||
allowed_origins:
|
||||
- https://example.com
|
||||
- https://*.example.com
|
||||
allowed_methods:
|
||||
- POST
|
||||
- GET
|
||||
- PUT
|
||||
- PATCH
|
||||
- DELETE
|
||||
allowed_headers:
|
||||
- Authorization
|
||||
- Content-Type
|
||||
exposed_headers:
|
||||
- Content-Type
|
||||
allow_credentials: true
|
||||
max_age: 10
|
||||
debug: true
|
||||
tls:
|
||||
key:
|
||||
path: /path/to/key.pem
|
||||
base64: LS0tLS1CRUdJTiBFTkNSWVBURUQgUFJJVkFURSBLRVktLS0tLVxuTUlJRkRqQkFCZ2txaGtpRzl3MEJCUTB3...
|
||||
cert:
|
||||
path: /path/to/cert.pem
|
||||
base64: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tXG5NSUlEWlRDQ0FrMmdBd0lCQWdJRVY1eE90REFOQmdr...
|
||||
|
||||
api:
|
||||
port: 1235
|
||||
host: 127.0.0.2
|
||||
|
||||
timeout:
|
||||
read: 1s
|
||||
write: 2s
|
||||
idle: 3s
|
||||
|
||||
cors:
|
||||
enabled: true
|
||||
allowed_origins:
|
||||
- https://example.org
|
||||
- https://*.example.org
|
||||
allowed_methods:
|
||||
- GET
|
||||
- PUT
|
||||
- PATCH
|
||||
- DELETE
|
||||
allowed_headers:
|
||||
- Authorization
|
||||
- Content-Type
|
||||
exposed_headers:
|
||||
- Content-Type
|
||||
allow_credentials: true
|
||||
max_age: 10
|
||||
debug: true
|
||||
tls:
|
||||
key:
|
||||
path: /path/to/key.pem
|
||||
base64: LS0tLS1CRUdJTiBFTkNSWVBURUQgUFJJVkFURSBLRVktLS0tLVxuTUlJRkRqQkFCZ2txaGtpRzl3MEJCUTB3...
|
||||
cert:
|
||||
path: /path/to/cert.pem
|
||||
base64: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tXG5NSUlEWlRDQ0FrMmdBd0lCQWdJRVY1eE90REFOQmdr...
|
||||
|
||||
prometheus:
|
||||
port: 9000
|
||||
host: localhost
|
||||
metrics_path: /metrics
|
||||
collapse_request_paths: true
|
||||
|
||||
# Configures Access Rules
|
||||
access_rules:
|
||||
# Locations (list of URLs) where access rules should be fetched from on boot.
|
||||
# It is expected that the documents at those locations return a JSON or YAML Array containing ORY Oathkeeper Access Rules.
|
||||
repositories:
|
||||
# If the URL Scheme is `file://`, the access rules (an array of access rules is expected) will be
|
||||
# fetched from the local file system.
|
||||
- file://path/to/rules.json
|
||||
# If the URL Scheme is `inline://`, the access rules (an array of access rules is expected)
|
||||
# are expected to be a base64 encoded (with padding!) JSON/YAML string (base64_encode(`[{"id":"foo-rule","authenticators":[....]}]`)):
|
||||
- inline://W3siaWQiOiJmb28tcnVsZSIsImF1dGhlbnRpY2F0b3JzIjpbXX1d
|
||||
# If the URL Scheme is `http://` or `https://`, the access rules (an array of access rules is expected) will be
|
||||
# fetched from the provided HTTP(s) location.
|
||||
- https://path-to-my-rules/rules.json
|
||||
# Optional fields describing matching strategy, defaults to "regexp".
|
||||
matching_strategy: glob
|
||||
|
||||
errors:
|
||||
fallback:
|
||||
- json
|
||||
handlers:
|
||||
redirect:
|
||||
enabled: true
|
||||
config:
|
||||
to: http://path-to/redirect
|
||||
json:
|
||||
enabled: true
|
||||
config:
|
||||
verbose: true
|
||||
when:
|
||||
- error:
|
||||
- unauthorized
|
||||
- forbidden
|
||||
- internal_server_error
|
||||
request:
|
||||
header:
|
||||
content_type:
|
||||
- application/json
|
||||
accept:
|
||||
- application/json
|
||||
cidr:
|
||||
- 127.0.0.0/24
|
||||
|
||||
# All authenticators can be configured under this configuration key
|
||||
authenticators:
|
||||
# Configures the anonymous authenticator
|
||||
anonymous:
|
||||
# Set enabled to true if the authenticator should be enabled and false to disable the authenticator. Defaults to false.
|
||||
enabled: true
|
||||
|
||||
config:
|
||||
# Sets the anonymous username. Defaults to "anonymous". Common names include "guest", "anon", "anonymous", "unknown".
|
||||
subject: guest
|
||||
|
||||
# Configures the cookie session authenticator
|
||||
cookie_session:
|
||||
# Set enabled to true if the authenticator should be enabled and false to disable the authenticator. Defaults to false.
|
||||
enabled: true
|
||||
|
||||
config:
|
||||
# Sets the origin to proxy requests to. If the response is a 200 with body `{ "subject": "...", "extra": {} }`
|
||||
# The request will pass the subject through successfully, otherwise it will be marked as unauthorized
|
||||
check_session_url: https://session-store-host
|
||||
|
||||
# Sets a list of possible cookies to look for on incoming requests, and will fallthrough to the next authenticator if
|
||||
# none of the passed cookies are set on the request
|
||||
only:
|
||||
- sessionid
|
||||
|
||||
# Configures the jwt authenticator
|
||||
jwt:
|
||||
# Set enabled to true if the authenticator should be enabled and false to disable the authenticator. Defaults to false.
|
||||
enabled: true
|
||||
|
||||
config:
|
||||
# REQUIRED IF ENABLED - The URL where ORY Oathkeeper can retrieve JSON Web Keys from for validating the JSON Web
|
||||
# Token. Usually something like "https://my-keys.com/.well-known/jwks.json". The response of that endpoint must
|
||||
# return a JSON Web Key Set (JWKS).
|
||||
jwks_urls:
|
||||
- https://my-website.com/.well-known/jwks.json
|
||||
- https://my-other-website.com/.well-known/jwks.json
|
||||
- file://path/to/local/jwks.json
|
||||
|
||||
# Sets the strategy to be used to validate/match the scope. Supports "hierarchic", "exact", "wildcard", "none". Defaults
|
||||
# to "none".
|
||||
scope_strategy: wildcard
|
||||
|
||||
# Configures the noop authenticator
|
||||
noop:
|
||||
# Set enabled to true if the authenticator should be enabled and false to disable the authenticator. Defaults to false.
|
||||
enabled: true
|
||||
|
||||
# Configures the oauth2_client_credentials authenticator
|
||||
oauth2_client_credentials:
|
||||
# Set enabled to true if the authenticator should be enabled and false to disable the authenticator. Defaults to false.
|
||||
enabled: true
|
||||
|
||||
config:
|
||||
# REQUIRED IF ENABLED - The OAuth 2.0 Token Endpoint that will be used to validate the client credentials.
|
||||
token_url: https://my-website.com/oauth2/token
|
||||
|
||||
# Configures the oauth2_introspection authenticator
|
||||
oauth2_introspection:
|
||||
# Set enabled to true if the authenticator should be enabled and false to disable the authenticator. Defaults to false.
|
||||
enabled: true
|
||||
|
||||
config:
|
||||
# REQUIRED IF ENABLED - The OAuth 2.0 Token Introspection endpoint.
|
||||
introspection_url: https://my-website.com/oauth2/introspection
|
||||
|
||||
# Sets the strategy to be used to validate/match the token scope. Supports "hierarchic", "exact", "wildcard", "none". Defaults
|
||||
# to "none".
|
||||
scope_strategy: exact
|
||||
|
||||
# Enable pre-authorization in cases where the OAuth 2.0 Token Introspection endpoint is protected by OAuth 2.0 Bearer
|
||||
# Tokens that can be retrieved using the OAuth 2.0 Client Credentials grant.
|
||||
pre_authorization:
|
||||
# Enable pre-authorization. Defaults to false.
|
||||
enabled: true
|
||||
|
||||
# REQUIRED IF ENABLED - The OAuth 2.0 Client ID to be used for the OAuth 2.0 Client Credentials Grant.
|
||||
client_id: some_id
|
||||
|
||||
# REQUIRED IF ENABLED - The OAuth 2.0 Client Secret to be used for the OAuth 2.0 Client Credentials Grant.
|
||||
client_secret: some_secret
|
||||
|
||||
# The OAuth 2.0 Scope to be requested during the OAuth 2.0 Client Credentials Grant.
|
||||
scope:
|
||||
- foo
|
||||
- bar
|
||||
|
||||
# REQUIRED IF ENABLED - The OAuth 2.0 Token Endpoint where the OAuth 2.0 Client Credentials Grant will be performed.
|
||||
token_url: https://my-website.com/oauth2/token
|
||||
|
||||
# Configures the unauthorized authenticator
|
||||
unauthorized:
|
||||
# Set enabled to true if the authenticator should be enabled and false to disable the authenticator. Defaults to false.
|
||||
enabled: true
|
||||
|
||||
# All authorizers can be configured under this configuration key
|
||||
authorizers:
|
||||
# Configures the allow authorizer
|
||||
allow:
|
||||
# Set enabled to true if the authorizer should be enabled and false to disable the authorizer. Defaults to false.
|
||||
enabled: true
|
||||
|
||||
# Configures the deny authorizer
|
||||
deny:
|
||||
# Set enabled to true if the authorizer should be enabled and false to disable the authorizer. Defaults to false.
|
||||
enabled: true
|
||||
|
||||
# Configures the keto_engine_acp_ory authorizer
|
||||
keto_engine_acp_ory:
|
||||
# Set enabled to true if the authorizer should be enabled and false to disable the authorizer. Defaults to false.
|
||||
enabled: true
|
||||
|
||||
config:
|
||||
# REQUIRED IF ENABLED - The base URL of ORY Keto, typically something like http(s)://<host>[:<port>]/
|
||||
base_url: http://my-keto/
|
||||
required_action: unknown
|
||||
required_resource: unknown
|
||||
|
||||
# Configures the remote authorizer
|
||||
remote:
|
||||
# Set enabled to true if the authorizer should be enabled and false to disable the authorizer. Defaults to false.
|
||||
enabled: true
|
||||
|
||||
config:
|
||||
remote: https://host/path
|
||||
headers: {}
|
||||
|
||||
# Configures the remote_json authorizer
|
||||
remote_json:
|
||||
# Set enabled to true if the authorizer should be enabled and false to disable the authorizer. Defaults to false.
|
||||
enabled: true
|
||||
|
||||
config:
|
||||
remote: https://host/path
|
||||
payload: "{}"
|
||||
|
||||
# All mutators can be configured under this configuration key
|
||||
mutators:
|
||||
header:
|
||||
enabled: true
|
||||
config:
|
||||
headers:
|
||||
foo: bar
|
||||
|
||||
# Configures the cookie mutator
|
||||
cookie:
|
||||
# Set enabled to true if the mutator should be enabled and false to disable the mutator. Defaults to false.
|
||||
enabled: true
|
||||
config:
|
||||
cookies:
|
||||
foo: bar
|
||||
|
||||
# Configures the hydrator mutator
|
||||
hydrator:
|
||||
# Set enabled to true if the mutator should be enabled and false to disable the mutator. Defaults to false.
|
||||
enabled: true
|
||||
|
||||
config:
|
||||
api:
|
||||
url: https://some-url/
|
||||
|
||||
# Configures the id_token mutator
|
||||
id_token:
|
||||
# Set enabled to true if the mutator should be enabled and false to disable the mutator. Defaults to false.
|
||||
enabled: true
|
||||
config:
|
||||
# REQUIRED IF ENABLED - Sets the "iss" value of the ID Token.
|
||||
issuer_url: https://my-oathkeeper/
|
||||
# REQUIRED IF ENABLED - Sets the URL where keys should be fetched from. Supports remote locations (http, https) as
|
||||
# well as local filesystem paths.
|
||||
jwks_url: https://fetch-keys/from/this/location.json
|
||||
# jwks_url: file:///from/this/absolute/location.json
|
||||
# jwks_url: file://../from/this/relative/location.json
|
||||
|
||||
# Sets the time-to-live of the ID token. Defaults to one minute. Valid time units are: s (second), m (minute), h (hour).
|
||||
ttl: 1h
|
||||
|
||||
# Configures the noop mutator
|
||||
noop:
|
||||
# Set enabled to true if the mutator should be enabled and false to disable the mutator. Defaults to false.
|
||||
enabled: true
|
||||
204
oryx/configx/stub/benchmark/schema.config.json
Normal file
204
oryx/configx/stub/benchmark/schema.config.json
Normal file
@@ -0,0 +1,204 @@
|
||||
{
|
||||
"log": {
|
||||
"level": "debug",
|
||||
"format": "json"
|
||||
},
|
||||
"profiling": "cpu",
|
||||
"serve": {
|
||||
"proxy": {
|
||||
"port": 1234,
|
||||
"host": "127.0.0.1",
|
||||
"timeout": {
|
||||
"read": "1s",
|
||||
"write": "2s",
|
||||
"idle": "3s"
|
||||
},
|
||||
"cors": {
|
||||
"enabled": true,
|
||||
"allowed_origins": ["https://example.com", "https://*.example.com"],
|
||||
"allowed_methods": ["POST", "GET", "PUT", "PATCH", "DELETE"],
|
||||
"allowed_headers": ["Authorization", "Content-Type"],
|
||||
"exposed_headers": ["Content-Type"],
|
||||
"allow_credentials": true,
|
||||
"max_age": 10,
|
||||
"debug": true
|
||||
},
|
||||
"tls": {
|
||||
"key": {
|
||||
"path": "/path/to/key.pem",
|
||||
"base64": "LS0tLS1CRUdJTiBFTkNSWVBURUQgUFJJVkFURSBLRVktLS0tLVxuTUlJRkRqQkFCZ2txaGtpRzl3MEJCUTB3..."
|
||||
},
|
||||
"cert": {
|
||||
"path": "/path/to/cert.pem",
|
||||
"base64": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tXG5NSUlEWlRDQ0FrMmdBd0lCQWdJRVY1eE90REFOQmdr..."
|
||||
}
|
||||
}
|
||||
},
|
||||
"api": {
|
||||
"port": 1235,
|
||||
"host": "127.0.0.2",
|
||||
"cors": {
|
||||
"enabled": true,
|
||||
"allowed_origins": ["https://example.org", "https://*.example.org"],
|
||||
"allowed_methods": ["GET", "PUT", "PATCH", "DELETE"],
|
||||
"allowed_headers": ["Authorization", "Content-Type"],
|
||||
"exposed_headers": ["Content-Type"],
|
||||
"allow_credentials": true,
|
||||
"max_age": 10,
|
||||
"debug": true
|
||||
},
|
||||
"tls": {
|
||||
"key": {
|
||||
"path": "/path/to/key.pem",
|
||||
"base64": "LS0tLS1CRUdJTiBFTkNSWVBURUQgUFJJVkFURSBLRVktLS0tLVxuTUlJRkRqQkFCZ2txaGtpRzl3MEJCUTB3..."
|
||||
},
|
||||
"cert": {
|
||||
"path": "/path/to/cert.pem",
|
||||
"base64": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tXG5NSUlEWlRDQ0FrMmdBd0lCQWdJRVY1eE90REFOQmdr..."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"access_rules": {
|
||||
"repositories": [
|
||||
"file://path/to/rules.json",
|
||||
"inline://W3siaWQiOiJmb28tcnVsZSIsImF1dGhlbnRpY2F0b3JzIjpbXX1d",
|
||||
"https://path-to-my-rules/rules.json"
|
||||
],
|
||||
"matching_strategy": "glob"
|
||||
},
|
||||
"errors": {
|
||||
"fallback": ["json"],
|
||||
"handlers": {
|
||||
"redirect": {
|
||||
"enabled": true,
|
||||
"config": {
|
||||
"to": "http://path-to/redirect"
|
||||
}
|
||||
},
|
||||
"json": {
|
||||
"enabled": true,
|
||||
"config": {
|
||||
"verbose": true,
|
||||
"when": [
|
||||
{
|
||||
"error": ["unauthorized", "forbidden", "internal_server_error"],
|
||||
"request": {
|
||||
"header": {
|
||||
"content_type": ["application/json"],
|
||||
"accept": ["application/json"]
|
||||
},
|
||||
"cidr": ["127.0.0.0/24"]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"authenticators": {
|
||||
"anonymous": {
|
||||
"enabled": true,
|
||||
"config": {
|
||||
"subject": "guest"
|
||||
}
|
||||
},
|
||||
"cookie_session": {
|
||||
"enabled": true,
|
||||
"config": {
|
||||
"check_session_url": "https://session-store-host",
|
||||
"only": ["sessionid"]
|
||||
}
|
||||
},
|
||||
"jwt": {
|
||||
"enabled": true,
|
||||
"config": {
|
||||
"jwks_urls": [
|
||||
"https://my-website.com/.well-known/jwks.json",
|
||||
"https://my-other-website.com/.well-known/jwks.json",
|
||||
"file://path/to/local/jwks.json"
|
||||
],
|
||||
"scope_strategy": "wildcard"
|
||||
}
|
||||
},
|
||||
"noop": {
|
||||
"enabled": true
|
||||
},
|
||||
"oauth2_client_credentials": {
|
||||
"enabled": true,
|
||||
"config": {
|
||||
"token_url": "https://my-website.com/oauth2/token"
|
||||
}
|
||||
},
|
||||
"oauth2_introspection": {
|
||||
"enabled": true,
|
||||
"config": {
|
||||
"introspection_url": "https://my-website.com/oauth2/introspection",
|
||||
"scope_strategy": "exact",
|
||||
"pre_authorization": {
|
||||
"enabled": true,
|
||||
"client_id": "some_id",
|
||||
"client_secret": "some_secret",
|
||||
"scope": ["foo", "bar"],
|
||||
"token_url": "https://my-website.com/oauth2/token"
|
||||
}
|
||||
}
|
||||
},
|
||||
"unauthorized": {
|
||||
"enabled": true
|
||||
}
|
||||
},
|
||||
"authorizers": {
|
||||
"allow": {
|
||||
"enabled": true
|
||||
},
|
||||
"deny": {
|
||||
"enabled": true
|
||||
},
|
||||
"keto_engine_acp_ory": {
|
||||
"enabled": true,
|
||||
"config": {
|
||||
"base_url": "http://my-keto/",
|
||||
"required_action": "unknown",
|
||||
"required_resource": "unknown"
|
||||
}
|
||||
}
|
||||
},
|
||||
"mutators": {
|
||||
"header": {
|
||||
"enabled": false,
|
||||
"config": {
|
||||
"headers": {
|
||||
"foo": "bar"
|
||||
}
|
||||
}
|
||||
},
|
||||
"cookie": {
|
||||
"enabled": true,
|
||||
"config": {
|
||||
"cookies": {
|
||||
"foo": "bar"
|
||||
}
|
||||
}
|
||||
},
|
||||
"hydrator": {
|
||||
"enabled": true,
|
||||
"config": {
|
||||
"api": {
|
||||
"url": "https://some-url/"
|
||||
}
|
||||
}
|
||||
},
|
||||
"id_token": {
|
||||
"enabled": true,
|
||||
"config": {
|
||||
"issuer_url": "https://my-oathkeeper/",
|
||||
"jwks_url": "https://fetch-keys/from/this/location.json",
|
||||
"ttl": "1h"
|
||||
}
|
||||
},
|
||||
"noop": {
|
||||
"enabled": true
|
||||
}
|
||||
}
|
||||
}
|
||||
41
oryx/configx/stub/domain-aliases/config.schema.json
Normal file
41
oryx/configx/stub/domain-aliases/config.schema.json
Normal file
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"domain_aliases": {
|
||||
"title": "Domain Aliases",
|
||||
"description": "Adds an alias domain. If a request with the hostname (FQDN) matching the hostname in the alias is found, that URL is used as the base URL.",
|
||||
"type": "array",
|
||||
"items": [
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"type": "object",
|
||||
"required": ["match_domain", "base_path", "scheme"],
|
||||
"properties": {
|
||||
"match_domain": {
|
||||
"minLength": 1,
|
||||
"title": "Matching Domain",
|
||||
"description": "Sets the matching domain. If the domain matches with this entry, the accompanying base_url will be used.",
|
||||
"type": "string",
|
||||
"examples": ["localhost", "my-domain.com"]
|
||||
},
|
||||
"scheme": {
|
||||
"title": "Scheme",
|
||||
"description": "Sets the scheme, for example https or http.",
|
||||
"type": "string",
|
||||
"enum": ["http", "https"]
|
||||
},
|
||||
"base_path": {
|
||||
"minLength": 1,
|
||||
"title": "Base Path",
|
||||
"description": "Sets the base path for the matched domain.",
|
||||
"type": "string",
|
||||
"default": "/",
|
||||
"pattern": "^/.*$",
|
||||
"examples": ["/", "/.ory/kratos"]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
27
oryx/configx/stub/from-files/a.yaml
Normal file
27
oryx/configx/stub/from-files/a.yaml
Normal file
@@ -0,0 +1,27 @@
|
||||
version: v0.5.3-alpha.1
|
||||
|
||||
dsn: memory
|
||||
|
||||
serve:
|
||||
public:
|
||||
base_url: http://127.0.0.1:4433/
|
||||
cors:
|
||||
enabled: true
|
||||
admin:
|
||||
base_url: http://kratos:4434/
|
||||
|
||||
selfservice:
|
||||
default_browser_return_url: http://127.0.0.1:4455/
|
||||
whitelisted_return_urls:
|
||||
- http://127.0.0.1:4455
|
||||
|
||||
methods:
|
||||
password:
|
||||
enabled: true
|
||||
|
||||
flows:
|
||||
error:
|
||||
ui_url: http://127.0.0.1:4455/error
|
||||
|
||||
settings:
|
||||
ui_url: http://127.0.0.1:4455/settings
|
||||
54
oryx/configx/stub/from-files/b.yaml
Normal file
54
oryx/configx/stub/from-files/b.yaml
Normal file
@@ -0,0 +1,54 @@
|
||||
selfservice:
|
||||
flows:
|
||||
settings:
|
||||
privileged_session_max_age: 15m
|
||||
|
||||
recovery:
|
||||
enabled: true
|
||||
ui_url: http://127.0.0.1:4455/recovery
|
||||
|
||||
verification:
|
||||
enabled: true
|
||||
ui_url: http://127.0.0.1:4455/verify
|
||||
after:
|
||||
default_browser_return_url: http://127.0.0.1:4455/
|
||||
|
||||
logout:
|
||||
after:
|
||||
default_browser_return_url: http://127.0.0.1:4455/auth/login
|
||||
|
||||
login:
|
||||
ui_url: http://127.0.0.1:4455/auth/login
|
||||
lifespan: 10m
|
||||
|
||||
registration:
|
||||
lifespan: 10m
|
||||
ui_url: http://127.0.0.1:4455/auth/registration
|
||||
after:
|
||||
password:
|
||||
hooks:
|
||||
- hook: session
|
||||
|
||||
log:
|
||||
level: debug
|
||||
format: text
|
||||
leak_sensitive_values: true
|
||||
|
||||
secrets:
|
||||
cookie:
|
||||
- PLEASE-CHANGE-ME-I-AM-VERY-INSECURE
|
||||
|
||||
hashers:
|
||||
argon2:
|
||||
parallelism: 1
|
||||
memory: 131072
|
||||
iterations: 2
|
||||
salt_length: 16
|
||||
key_length: 16
|
||||
|
||||
identity:
|
||||
default_schema_url: file:///etc/config/kratos/identity.schema.json
|
||||
|
||||
courier:
|
||||
smtp:
|
||||
connection_uri: smtps://test:test@mailslurper:1025/?skip_ssl_verify=true
|
||||
1085
oryx/configx/stub/from-files/config.schema.json
Normal file
1085
oryx/configx/stub/from-files/config.schema.json
Normal file
File diff suppressed because it is too large
Load Diff
124
oryx/configx/stub/from-files/expected.json
Normal file
124
oryx/configx/stub/from-files/expected.json
Normal file
@@ -0,0 +1,124 @@
|
||||
{
|
||||
"courier": {
|
||||
"smtp": {
|
||||
"connection_uri": "smtps://test:test@mailslurper:1025/?skip_ssl_verify=true",
|
||||
"from_address": "no-reply@ory.kratos.sh"
|
||||
}
|
||||
},
|
||||
"dsn": "sqlite:///var/lib/sqlite/db.sqlite?_fk=true",
|
||||
"hashers": {
|
||||
"argon2": {
|
||||
"iterations": 2,
|
||||
"key_length": 16,
|
||||
"memory": 131072,
|
||||
"parallelism": 1,
|
||||
"salt_length": 16
|
||||
}
|
||||
},
|
||||
"identity": {
|
||||
"default_schema_url": "file:///etc/config/kratos/identity.schema.json"
|
||||
},
|
||||
"log": {
|
||||
"format": "text",
|
||||
"leak_sensitive_values": true,
|
||||
"level": "debug"
|
||||
},
|
||||
"secrets": {
|
||||
"cookie": ["PLEASE-CHANGE-ME-I-AM-VERY-INSECURE"]
|
||||
},
|
||||
"selfservice": {
|
||||
"default_browser_return_url": "http://127.0.0.1:4455/",
|
||||
"flows": {
|
||||
"error": {
|
||||
"ui_url": "http://127.0.0.1:4455/error"
|
||||
},
|
||||
"login": {
|
||||
"lifespan": "10m",
|
||||
"ui_url": "http://127.0.0.1:4455/auth/login"
|
||||
},
|
||||
"logout": {
|
||||
"after": {
|
||||
"default_browser_return_url": "http://127.0.0.1:4455/auth/login"
|
||||
}
|
||||
},
|
||||
"recovery": {
|
||||
"enabled": true,
|
||||
"lifespan": "1h",
|
||||
"ui_url": "http://127.0.0.1:4455/recovery"
|
||||
},
|
||||
"registration": {
|
||||
"after": {
|
||||
"password": {
|
||||
"hooks": [
|
||||
{
|
||||
"hook": "session"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"lifespan": "10m",
|
||||
"ui_url": "http://127.0.0.1:4455/auth/registration"
|
||||
},
|
||||
"settings": {
|
||||
"lifespan": "1h",
|
||||
"privileged_session_max_age": "15m",
|
||||
"ui_url": "http://127.0.0.1:4455/settings"
|
||||
},
|
||||
"verification": {
|
||||
"after": {
|
||||
"default_browser_return_url": "http://127.0.0.1:4455/"
|
||||
},
|
||||
"enabled": true,
|
||||
"lifespan": "1h",
|
||||
"ui_url": "http://127.0.0.1:4455/verify"
|
||||
}
|
||||
},
|
||||
"methods": {
|
||||
"link": {
|
||||
"enabled": true
|
||||
},
|
||||
"oidc": {
|
||||
"enabled": false
|
||||
},
|
||||
"password": {
|
||||
"enabled": true
|
||||
},
|
||||
"profile": {
|
||||
"enabled": true
|
||||
}
|
||||
},
|
||||
"whitelisted_return_urls": ["http://127.0.0.1:4455"]
|
||||
},
|
||||
"serve": {
|
||||
"admin": {
|
||||
"base_url": "http://kratos:4434/",
|
||||
"host": "0.0.0.0",
|
||||
"port": 4434
|
||||
},
|
||||
"public": {
|
||||
"base_url": "http://127.0.0.1:4433/",
|
||||
"cors": {
|
||||
"allow_credentials": true,
|
||||
"allowed_headers": ["Authorization", "Content-Type", "X-Session-Token"],
|
||||
"allowed_methods": ["POST", "GET", "PUT", "PATCH", "DELETE"],
|
||||
"allowed_origins": ["*"],
|
||||
"debug": false,
|
||||
"enabled": true,
|
||||
"exposed_headers": ["Content-Type"],
|
||||
"max_age": 0,
|
||||
"options_passthrough": false
|
||||
},
|
||||
"host": "0.0.0.0",
|
||||
"port": 4433
|
||||
}
|
||||
},
|
||||
"session": {
|
||||
"cookie": {
|
||||
"path": "/",
|
||||
"persistent": true,
|
||||
"same_site": "Lax"
|
||||
},
|
||||
"lifespan": "24h"
|
||||
},
|
||||
"version": "v0.5.3-alpha.1"
|
||||
}
|
||||
792
oryx/configx/stub/hydra/config.schema.json
Normal file
792
oryx/configx/stub/hydra/config.schema.json
Normal file
@@ -0,0 +1,792 @@
|
||||
{
|
||||
"$id": "https://github.com/ory/hydra/docs/config.schema.json",
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "ORY Hydra Configuration",
|
||||
"type": "object",
|
||||
"definitions": {
|
||||
"http_method": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"POST",
|
||||
"GET",
|
||||
"PUT",
|
||||
"PATCH",
|
||||
"DELETE",
|
||||
"CONNECT",
|
||||
"HEAD",
|
||||
"OPTIONS",
|
||||
"TRACE"
|
||||
]
|
||||
},
|
||||
"port_number": {
|
||||
"type": "integer",
|
||||
"description": "The port to listen on.",
|
||||
"minimum": 1,
|
||||
"maximum": 65535
|
||||
},
|
||||
"socket": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"description": "Sets the permissions of the unix socket",
|
||||
"properties": {
|
||||
"owner": {
|
||||
"type": "string",
|
||||
"description": "Owner of unix socket. If empty, the owner will be the user running hydra.",
|
||||
"default": ""
|
||||
},
|
||||
"group": {
|
||||
"type": "string",
|
||||
"description": "Group of unix socket. If empty, the group will be the primary group of the user running hydra.",
|
||||
"default": ""
|
||||
},
|
||||
"mode": {
|
||||
"type": "integer",
|
||||
"description": "Mode of unix socket in numeric form",
|
||||
"default": 493,
|
||||
"minimum": 0,
|
||||
"maximum": 511
|
||||
}
|
||||
}
|
||||
},
|
||||
"cors": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"description": "Configures Cross Origin Resource Sharing for public endpoints.",
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"type": "boolean",
|
||||
"description": "Sets whether CORS is enabled.",
|
||||
"default": false
|
||||
},
|
||||
"allowed_origins": {
|
||||
"type": "array",
|
||||
"description": "A list of origins a cross-domain request can be executed from. If the special * value is present in the list, all origins will be allowed. An origin may contain a wildcard (*) to replace 0 or more characters (i.e.: http://*.domain.com). Only one wildcard can be used per origin.",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"not": {
|
||||
"type": "string",
|
||||
"description": "does match all strings that contain two or more (*)",
|
||||
"pattern": ".*\\*.*\\*.*"
|
||||
},
|
||||
"anyOf": [
|
||||
{
|
||||
"format": "uri"
|
||||
},
|
||||
{
|
||||
"const": "*"
|
||||
}
|
||||
]
|
||||
},
|
||||
"uniqueItems": true,
|
||||
"default": ["*"],
|
||||
"examples": [
|
||||
[
|
||||
"https://example.com",
|
||||
"https://*.example.com",
|
||||
"https://*.foo.example.com"
|
||||
]
|
||||
]
|
||||
},
|
||||
"allowed_methods": {
|
||||
"type": "array",
|
||||
"description": "A list of HTTP methods the user agent is allowed to use with cross-domain requests.",
|
||||
"default": ["POST", "GET", "PUT", "PATCH", "DELETE"],
|
||||
"items": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"POST",
|
||||
"GET",
|
||||
"PUT",
|
||||
"PATCH",
|
||||
"DELETE",
|
||||
"CONNECT",
|
||||
"HEAD",
|
||||
"OPTIONS",
|
||||
"TRACE"
|
||||
]
|
||||
}
|
||||
},
|
||||
"allowed_headers": {
|
||||
"type": "array",
|
||||
"description": "A list of non simple headers the client is allowed to use with cross-domain requests.",
|
||||
"default": ["Authorization", "Content-Type"],
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"exposed_headers": {
|
||||
"type": "array",
|
||||
"description": "Sets which headers are safe to expose to the API of a CORS API specification.",
|
||||
"default": ["Content-Type"],
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"allow_credentials": {
|
||||
"type": "boolean",
|
||||
"description": "Sets whether the request can include user credentials like cookies, HTTP authentication or client side SSL certificates.",
|
||||
"default": true
|
||||
},
|
||||
"options_passthrough": {
|
||||
"type": "boolean",
|
||||
"description": "TODO",
|
||||
"default": false
|
||||
},
|
||||
"max_age": {
|
||||
"type": "integer",
|
||||
"description": "Sets how long (in seconds) the results of a preflight request can be cached. If set to 0, every request is preceded by a preflight request.",
|
||||
"default": 0,
|
||||
"minimum": 0
|
||||
},
|
||||
"debug": {
|
||||
"type": "boolean",
|
||||
"description": "Adds additional log output to debug server side CORS issues.",
|
||||
"default": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"pem_file": {
|
||||
"type": "object",
|
||||
"oneOf": [
|
||||
{
|
||||
"properties": {
|
||||
"path": {
|
||||
"type": "string",
|
||||
"description": "The path to the pem file.",
|
||||
"examples": ["/path/to/file.pem"]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": ["path"]
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"base64": {
|
||||
"type": "string",
|
||||
"description": "The base64 encoded string (without padding).",
|
||||
"contentEncoding": "base64",
|
||||
"contentMediaType": "application/x-pem-file",
|
||||
"examples": ["b3J5IGh5ZHJhIGlzIGF3ZXNvbWUK"]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": ["base64"]
|
||||
}
|
||||
]
|
||||
},
|
||||
"duration": {
|
||||
"type": "string",
|
||||
"pattern": "^[0-9]+(ns|us|ms|s|m|h)$",
|
||||
"examples": ["1h"]
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"log": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"description": "Configures the logger",
|
||||
"properties": {
|
||||
"level": {
|
||||
"type": "string",
|
||||
"description": "Sets the log level.",
|
||||
"enum": ["panic", "fatal", "error", "warn", "info", "debug", "trace"],
|
||||
"default": "info"
|
||||
},
|
||||
"leak_sensitive_values": {
|
||||
"type": "boolean",
|
||||
"description": "Logs sensitive values such as cookie and URL parameter.",
|
||||
"default": false
|
||||
},
|
||||
"redaction_text": {
|
||||
"type": "string",
|
||||
"title": "Sensitive log value redaction text",
|
||||
"description": "Text to use, when redacting sensitive log value."
|
||||
},
|
||||
"format": {
|
||||
"type": "string",
|
||||
"description": "Sets the log format.",
|
||||
"enum": ["json", "json_pretty", "text"],
|
||||
"default": "text"
|
||||
}
|
||||
}
|
||||
},
|
||||
"serve": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"description": "Controls the configuration for the http(s) daemon(s).",
|
||||
"properties": {
|
||||
"public": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"description": "Controls the public daemon serving public API endpoints like /oauth2/auth, /oauth2/token, /.well-known/jwks.json",
|
||||
"properties": {
|
||||
"port": {
|
||||
"default": 4444,
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/port_number"
|
||||
}
|
||||
]
|
||||
},
|
||||
"host": {
|
||||
"type": "string",
|
||||
"description": "The interface or unix socket ORY Hydra should listen and handle public API requests on. Use the prefix \"unix:\" to specify a path to a unix socket. Leave empty to listen on all interfaces.",
|
||||
"default": "",
|
||||
"examples": ["localhost"]
|
||||
},
|
||||
"cors": {
|
||||
"$ref": "#/definitions/cors"
|
||||
},
|
||||
"socket": {
|
||||
"$ref": "#/definitions/socket"
|
||||
},
|
||||
"access_log": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"description": "Access Log configuration for public server.",
|
||||
"properties": {
|
||||
"disable_for_health": {
|
||||
"type": "boolean",
|
||||
"description": "Disable access log for health endpoints.",
|
||||
"default": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"admin": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"port": {
|
||||
"default": 4445,
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/port_number"
|
||||
}
|
||||
]
|
||||
},
|
||||
"host": {
|
||||
"type": "string",
|
||||
"description": "The interface or unix socket ORY Hydra should listen and handle administrative API requests on. Use the prefix \"unix:\" to specify a path to a unix socket. Leave empty to listen on all interfaces.",
|
||||
"default": "",
|
||||
"examples": ["localhost"]
|
||||
},
|
||||
"cors": {
|
||||
"$ref": "#/definitions/cors"
|
||||
},
|
||||
"socket": {
|
||||
"$ref": "#/definitions/socket"
|
||||
},
|
||||
"access_log": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"description": "Access Log configuration for admin server.",
|
||||
"properties": {
|
||||
"disable_for_health": {
|
||||
"type": "boolean",
|
||||
"description": "Disable access log for health endpoints.",
|
||||
"default": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tls": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"description": "Configures HTTPS (HTTP over TLS). If configured, the server automatically supports HTTP/2.",
|
||||
"properties": {
|
||||
"key": {
|
||||
"description": "Configures the private key (pem encoded).",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/pem_file"
|
||||
}
|
||||
]
|
||||
},
|
||||
"cert": {
|
||||
"description": "Configures the private key (pem encoded).",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/pem_file"
|
||||
}
|
||||
]
|
||||
},
|
||||
"allow_termination_from": {
|
||||
"type": "array",
|
||||
"description": "Whitelist one or multiple CIDR address ranges and allow them to terminate TLS connections. Be aware that the X-Forwarded-Proto header must be set and must never be modifiable by anyone but your proxy / gateway / load balancer. Supports ipv4 and ipv6. Hydra serves http instead of https when this option is set.",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"oneOf": [
|
||||
{
|
||||
"pattern": "^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))/([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8])$"
|
||||
},
|
||||
{
|
||||
"pattern": "^([0-9]{1,3}\\.){3}[0-9]{1,3}/([0-9]|[1-2][0-9]|3[0-2])$"
|
||||
}
|
||||
],
|
||||
"examples": ["127.0.0.1/32"]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"cookies": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"same_site_mode": {
|
||||
"type": "string",
|
||||
"description": "Specify the SameSite mode that cookies should be sent with.",
|
||||
"enum": ["Strict", "Lax", "None"],
|
||||
"default": "None"
|
||||
},
|
||||
"same_site_legacy_workaround": {
|
||||
"type": "boolean",
|
||||
"description": "Some older browser versions don’t work with SameSite=None. This option enables the workaround defined in https://web.dev/samesite-cookie-recipes/ which essentially stores a second cookie without SameSite as a fallback.",
|
||||
"default": false,
|
||||
"examples": [true]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"dsn": {
|
||||
"type": "string",
|
||||
"description": "Sets the data source name. This configures the backend where ORY Hydra persists data. If dsn is \"memory\", data will be written to memory and is lost when you restart this instance. ORY Hydra supports popular SQL databases. For more detailed configuration information go to: https://www.ory.sh/docs/hydra/dependencies-environment#sql"
|
||||
},
|
||||
"webfinger": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"description": "Configures ./well-known/ settings.",
|
||||
"properties": {
|
||||
"jwks": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"description": "Configures the /.well-known/jwks.json endpoint.",
|
||||
"properties": {
|
||||
"broadcast_keys": {
|
||||
"type": "array",
|
||||
"description": "A list of JSON Web Keys that should be exposed at that endpoint. This is usually the public key for verifying OpenID Connect ID Tokens. However, you might want to add additional keys here as well.",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"default": ["hydra.openid.id-token"],
|
||||
"examples": ["hydra.jwt.access-token"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"oidc_discovery": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"description": "Configures OpenID Connect Discovery (/.well-known/openid-configuration).",
|
||||
"properties": {
|
||||
"jwks_url": {
|
||||
"type": "string",
|
||||
"description": "Overwrites the JWKS URL",
|
||||
"format": "uri",
|
||||
"examples": ["https://my-service.com/.well-known/jwks.json"]
|
||||
},
|
||||
"token_url": {
|
||||
"type": "string",
|
||||
"description": "Overwrites the OAuth2 Token URL",
|
||||
"format": "uri",
|
||||
"examples": ["https://my-service.com/oauth2/token"]
|
||||
},
|
||||
"auth_url": {
|
||||
"type": "string",
|
||||
"description": "Overwrites the OAuth2 Auth URL",
|
||||
"format": "uri",
|
||||
"examples": ["https://my-service.com/oauth2/auth"]
|
||||
},
|
||||
"client_registration_url": {
|
||||
"description": "Sets the OpenID Connect Dynamic Client Registration Endpoint",
|
||||
"type": "string",
|
||||
"format": "uri",
|
||||
"examples": ["https://my-service.com/clients"]
|
||||
},
|
||||
"supported_claims": {
|
||||
"type": "array",
|
||||
"description": "A list of supported claims to be broadcasted. Claim \"sub\" is always included.",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"examples": [["email", "username"]]
|
||||
},
|
||||
"supported_scope": {
|
||||
"type": "array",
|
||||
"description": "The scope OAuth 2.0 Clients may request. Scope `offline`, `offline_access`, and `openid` are always included.",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"examples": [["email", "whatever", "read.photos"]]
|
||||
},
|
||||
"userinfo_url": {
|
||||
"type": "string",
|
||||
"description": "A URL of the userinfo endpoint to be advertised at the OpenID Connect Discovery endpoint /.well-known/openid-configuration. Defaults to ORY Hydra's userinfo endpoint at /userinfo. Set this value if you want to handle this endpoint yourself.",
|
||||
"format": "uri",
|
||||
"examples": ["https://example.org/my-custom-userinfo-endpoint"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"oidc": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"description": "Configures OpenID Connect features.",
|
||||
"properties": {
|
||||
"subject_identifiers": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"description": "Configures the Subject Identifier algorithm. For more information please head over to the documentation: https://www.ory.sh/docs/hydra/advanced#subject-identifier-algorithms",
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"type": "array",
|
||||
"description": "A list of algorithms to enable.",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"enum": ["public", "pairwise"]
|
||||
}
|
||||
},
|
||||
"pairwise": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"description": "Configures the pairwise algorithm.",
|
||||
"properties": {
|
||||
"salt": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["salt"]
|
||||
}
|
||||
},
|
||||
"if": {
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"contains": {
|
||||
"const": "pairwise"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"then": {
|
||||
"required": ["pairwise"]
|
||||
},
|
||||
"else": {
|
||||
"properties": {
|
||||
"pairwise": {
|
||||
"$comment": "This enforces pairwise to not be set if 'enabled' does not contain 'pairwise'",
|
||||
"not": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
"examples": [
|
||||
{
|
||||
"enabled": ["public", "pairwise"],
|
||||
"pairwise": {
|
||||
"salt": "some-random-salt"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"dynamic_client_registration": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"description": "Configures OpenID Connect Dynamic Client Registration (exposed as admin endpoints /clients/...).",
|
||||
"properties": {
|
||||
"default_scope": {
|
||||
"type": "array",
|
||||
"description": "The OpenID Connect Dynamic Client Registration specification has no concept of whitelisting OAuth 2.0 Scope. If you want to expose Dynamic Client Registration, you should set the default scope enabled for newly registered clients. Keep in mind that users can overwrite this default by setting the \"scope\" key in the registration payload, effectively disabling the concept of whitelisted scopes.",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"examples": [["openid", "offline", "offline_access"]]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"urls": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"self": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"issuer": {
|
||||
"type": "string",
|
||||
"description": "This value will be used as the \"issuer\" in access and ID tokens. It must be specified and using HTTPS protocol, unless --dangerous-force-http is set. This should typically be equal to the public value.",
|
||||
"format": "uri",
|
||||
"examples": ["https://localhost:4444/"]
|
||||
},
|
||||
"public": {
|
||||
"type": "string",
|
||||
"description": "This is the base location of the public endpoints of your ORY Hydra installation. This should typically be equal to the issuer value. If left unspecified, it falls back to the issuer value.",
|
||||
"format": "uri",
|
||||
"examples": ["https://localhost:4444/"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"login": {
|
||||
"type": "string",
|
||||
"description": "Sets the login endpoint of the User Login & Consent flow. Defaults to an internal fallback URL showing an error.",
|
||||
"format": "uri",
|
||||
"examples": ["https://my-login.app/login"]
|
||||
},
|
||||
"consent": {
|
||||
"type": "string",
|
||||
"description": "Sets the consent endpoint of the User Login & Consent flow. Defaults to an internal fallback URL showing an error.",
|
||||
"format": "uri",
|
||||
"examples": ["https://my-consent.app/consent"]
|
||||
},
|
||||
"logout": {
|
||||
"type": "string",
|
||||
"description": "Sets the logout endpoint. Defaults to an internal fallback URL showing an error.",
|
||||
"format": "uri",
|
||||
"examples": ["https://my-logout.app/logout"]
|
||||
},
|
||||
"error": {
|
||||
"type": "string",
|
||||
"description": "Sets the error endpoint. The error ui will be shown when an OAuth2 error occurs that which can not be sent back to the client. Defaults to an internal fallback URL showing an error.",
|
||||
"format": "uri",
|
||||
"examples": ["https://my-error.app/error"]
|
||||
},
|
||||
"post_logout_redirect": {
|
||||
"type": "string",
|
||||
"description": "When a user agent requests to logout, it will be redirected to this url afterwards per default.",
|
||||
"format": "uri",
|
||||
"examples": ["https://my-example.app/logout-successful"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"strategies": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"scope": {
|
||||
"type": "string",
|
||||
"description": "Defines how scopes are matched. For more details have a look at https://github.com/ory/fosite#scopes",
|
||||
"enum": [
|
||||
"exact",
|
||||
"wildcard",
|
||||
"DEPRECATED_HIERARCHICAL_SCOPE_STRATEGY"
|
||||
],
|
||||
"default": "wildcard"
|
||||
},
|
||||
"access_token": {
|
||||
"type": "string",
|
||||
"description": "Defines access token type. jwt is a bad idea, see https://www.ory.sh/docs/hydra/advanced#json-web-tokens",
|
||||
"enum": ["opaque", "jwt"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"ttl": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"description": "Configures time to live.",
|
||||
"properties": {
|
||||
"login_consent_request": {
|
||||
"description": "Configures how long a user login and consent flow may take.",
|
||||
"default": "1h",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/duration"
|
||||
}
|
||||
]
|
||||
},
|
||||
"access_token": {
|
||||
"description": "Configures how long access tokens are valid.",
|
||||
"default": "1h",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/duration"
|
||||
}
|
||||
]
|
||||
},
|
||||
"refresh_token": {
|
||||
"description": "Configures how long refresh tokens are valid. Set to -1 for refresh tokens to never expire.",
|
||||
"default": "720h",
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/definitions/duration"
|
||||
},
|
||||
{
|
||||
"enum": ["-1", -1]
|
||||
}
|
||||
]
|
||||
},
|
||||
"id_token": {
|
||||
"description": "Configures how long id tokens are valid.",
|
||||
"default": "1h",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/duration"
|
||||
}
|
||||
]
|
||||
},
|
||||
"auth_code": {
|
||||
"description": "Configures how long auth codes are valid.",
|
||||
"default": "10m",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/duration"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"oauth2": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"expose_internal_errors": {
|
||||
"type": "boolean",
|
||||
"description": "Set this to true if you want to share error debugging information with your OAuth 2.0 clients. Keep in mind that debug information is very valuable when dealing with errors, but might also expose database error codes and similar errors.",
|
||||
"default": false,
|
||||
"examples": [true]
|
||||
},
|
||||
"session": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"encrypt_at_rest": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"title": "Encrypt OAuth2 Session",
|
||||
"description": "If set to true (default) ORY Hydra encrypt OAuth2 and OpenID Connect session data using AES-GCM and the system secret before persisting it in the database."
|
||||
}
|
||||
}
|
||||
},
|
||||
"include_legacy_error_fields": {
|
||||
"type": "boolean",
|
||||
"description": "Set this to true if you want to include the `error_hint` and `error_debug` legacy fields in error responses. We recommend to set this to `false` unless you have clients using these fields.",
|
||||
"default": false,
|
||||
"examples": [true]
|
||||
},
|
||||
"hashers": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"description": "Configures hashing algorithms. Supports only BCrypt at the moment.",
|
||||
"properties": {
|
||||
"bcrypt": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"description": "Configures the BCrypt hashing algorithm used for hashing Client Secrets.",
|
||||
"properties": {
|
||||
"cost": {
|
||||
"type": "integer",
|
||||
"description": "Sets the BCrypt cost. The higher the value, the more CPU time is being used to generate hashes.",
|
||||
"default": 10,
|
||||
"minimum": 4,
|
||||
"maximum": 31
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"pkce": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"enforced": {
|
||||
"type": "boolean",
|
||||
"description": "Sets whether PKCE should be enforced for all clients.",
|
||||
"examples": [true]
|
||||
},
|
||||
"enforced_for_public_clients": {
|
||||
"type": "boolean",
|
||||
"description": "Sets whether PKCE should be enforced for public clients.",
|
||||
"examples": [true]
|
||||
}
|
||||
}
|
||||
},
|
||||
"client_credentials": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"default_grant_allowed_scope": {
|
||||
"type": "boolean",
|
||||
"description": "Defines how scopes are added if the request doesn't contains any scope",
|
||||
"examples": [false]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"secrets": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"description": "The secrets section configures secrets used for encryption and signing of several systems. All secrets can be rotated, for more information on this topic go to: https://www.ory.sh/docs/hydra/advanced#rotation-of-hmac-token-signing-and-database-and-cookie-encryption-keys",
|
||||
"properties": {
|
||||
"system": {
|
||||
"description": "The system secret must be at least 16 characters long. If none is provided, one will be generated. They key is used to encrypt sensitive data using AES-GCM (256 bit) and validate HMAC signatures. The first item in the list is used for signing and encryption. The whole list is used for verifying signatures and decryption.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"minLength": 16
|
||||
},
|
||||
"examples": [
|
||||
[
|
||||
"this-is-the-primary-secret",
|
||||
"this-is-an-old-secret",
|
||||
"this-is-another-old-secret"
|
||||
]
|
||||
]
|
||||
},
|
||||
"cookie": {
|
||||
"type": "array",
|
||||
"description": "A secret that is used to encrypt cookie sessions. Defaults to secrets.system. It is recommended to use a separate secret in production. The first item in the list is used for signing and encryption. The whole list is used for verifying signatures and decryption.",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"minLength": 16
|
||||
},
|
||||
"examples": [
|
||||
[
|
||||
"this-is-the-primary-secret",
|
||||
"this-is-an-old-secret",
|
||||
"this-is-another-old-secret"
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"profiling": {
|
||||
"type": "string",
|
||||
"description": "Enables profiling if set. For more details on profiling, head over to: https://blog.golang.org/profiling-go-programs",
|
||||
"enum": ["cpu", "mem"],
|
||||
"examples": ["cpu"]
|
||||
},
|
||||
"tracing": {
|
||||
"$ref": "ory://tracing-config"
|
||||
},
|
||||
"version": {
|
||||
"type": "string",
|
||||
"title": "The Hydra version this config is written for.",
|
||||
"description": "SemVer according to https://semver.org/ prefixed with `v` as in our releases.",
|
||||
"pattern": "^v(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$"
|
||||
},
|
||||
"cgroups": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"description": "ORY Hydra can respect Linux container CPU quota",
|
||||
"properties": {
|
||||
"v1": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"description": "Configures parameters using cgroups v1 hierarchy",
|
||||
"properties": {
|
||||
"auto_max_procs_enabled": {
|
||||
"type": "boolean",
|
||||
"description": "Set GOMAXPROCS automatically according to cgroups limits",
|
||||
"default": false,
|
||||
"examples": [true]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["dsn"]
|
||||
}
|
||||
122
oryx/configx/stub/hydra/expected.json
Normal file
122
oryx/configx/stub/hydra/expected.json
Normal file
@@ -0,0 +1,122 @@
|
||||
{
|
||||
"cgroups": {
|
||||
"v1": {
|
||||
"auto_max_procs_enabled": false
|
||||
}
|
||||
},
|
||||
"dsn": "sqlite:///var/lib/sqlite/db.sqlite?_fk=true",
|
||||
"log": {
|
||||
"format": "text",
|
||||
"leak_sensitive_values": false,
|
||||
"level": "info"
|
||||
},
|
||||
"oauth2": {
|
||||
"expose_internal_errors": false,
|
||||
"hashers": {
|
||||
"bcrypt": {
|
||||
"cost": 10
|
||||
}
|
||||
},
|
||||
"include_legacy_error_fields": false,
|
||||
"session": {
|
||||
"encrypt_at_rest": true
|
||||
}
|
||||
},
|
||||
"oidc": {
|
||||
"subject_identifiers": {
|
||||
"enabled": ["pairwise", "public"],
|
||||
"pairwise": {
|
||||
"salt": "youReallyNeedToChangeThis"
|
||||
}
|
||||
}
|
||||
},
|
||||
"secrets": {
|
||||
"system": ["youReallyNeedToChangeThis"]
|
||||
},
|
||||
"serve": {
|
||||
"admin": {
|
||||
"access_log": {
|
||||
"disable_for_health": false
|
||||
},
|
||||
"cors": {
|
||||
"allow_credentials": true,
|
||||
"allowed_headers": ["Authorization", "Content-Type"],
|
||||
"allowed_methods": ["POST", "GET", "PUT", "PATCH", "DELETE"],
|
||||
"allowed_origins": ["*"],
|
||||
"debug": false,
|
||||
"enabled": false,
|
||||
"exposed_headers": ["Content-Type"],
|
||||
"max_age": 0,
|
||||
"options_passthrough": false
|
||||
},
|
||||
"host": "",
|
||||
"port": 4445,
|
||||
"socket": {
|
||||
"group": "",
|
||||
"mode": 493,
|
||||
"owner": ""
|
||||
}
|
||||
},
|
||||
"cookies": {
|
||||
"same_site_legacy_workaround": false,
|
||||
"same_site_mode": "Lax"
|
||||
},
|
||||
"public": {
|
||||
"access_log": {
|
||||
"disable_for_health": false
|
||||
},
|
||||
"cors": {
|
||||
"allow_credentials": true,
|
||||
"allowed_headers": ["Authorization", "Content-Type"],
|
||||
"allowed_methods": ["POST", "GET", "PUT", "PATCH", "DELETE"],
|
||||
"allowed_origins": ["*"],
|
||||
"debug": false,
|
||||
"enabled": false,
|
||||
"exposed_headers": ["Content-Type"],
|
||||
"max_age": 0,
|
||||
"options_passthrough": false
|
||||
},
|
||||
"host": "",
|
||||
"port": 4444,
|
||||
"socket": {
|
||||
"group": "",
|
||||
"mode": 493,
|
||||
"owner": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"strategies": {
|
||||
"scope": "wildcard"
|
||||
},
|
||||
"tracing": {
|
||||
"provider": "jaeger",
|
||||
"providers": {
|
||||
"jaeger": {
|
||||
"local_agent_address": "jaeger:6831",
|
||||
"sampling": {
|
||||
"server_url": "http://jaeger:5778/sampling"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"ttl": {
|
||||
"access_token": "1h",
|
||||
"auth_code": "10m",
|
||||
"id_token": "1h",
|
||||
"login_consent_request": "1h",
|
||||
"refresh_token": "720h"
|
||||
},
|
||||
"urls": {
|
||||
"consent": "http://127.0.0.1:3000/consent",
|
||||
"login": "http://127.0.0.1:3000/login",
|
||||
"logout": "http://127.0.0.1:3000/logout",
|
||||
"self": {
|
||||
"issuer": "http://127.0.0.1:4444"
|
||||
}
|
||||
},
|
||||
"webfinger": {
|
||||
"jwks": {
|
||||
"broadcast_keys": ["hydra.openid.id-token"]
|
||||
}
|
||||
}
|
||||
}
|
||||
22
oryx/configx/stub/hydra/hydra.yaml
Normal file
22
oryx/configx/stub/hydra/hydra.yaml
Normal file
@@ -0,0 +1,22 @@
|
||||
serve:
|
||||
cookies:
|
||||
same_site_mode: Lax
|
||||
|
||||
urls:
|
||||
self:
|
||||
issuer: http://127.0.0.1:4444
|
||||
consent: http://127.0.0.1:3000/consent
|
||||
login: http://127.0.0.1:3000/login
|
||||
logout: http://127.0.0.1:3000/logout
|
||||
|
||||
secrets:
|
||||
system:
|
||||
- youReallyNeedToChangeThis
|
||||
|
||||
oidc:
|
||||
subject_identifiers:
|
||||
enabled:
|
||||
- pairwise
|
||||
- public
|
||||
pairwise:
|
||||
salt: youReallyNeedToChangeThis
|
||||
1085
oryx/configx/stub/kratos/config.schema.json
Normal file
1085
oryx/configx/stub/kratos/config.schema.json
Normal file
File diff suppressed because it is too large
Load Diff
135
oryx/configx/stub/kratos/expected.json
Normal file
135
oryx/configx/stub/kratos/expected.json
Normal file
@@ -0,0 +1,135 @@
|
||||
{
|
||||
"courier": {
|
||||
"smtp": {
|
||||
"connection_uri": "smtps://test:test@mailslurper:1025/?skip_ssl_verify=true",
|
||||
"from_address": "no-reply@ory.kratos.sh"
|
||||
}
|
||||
},
|
||||
"dsn": "sqlite:///var/lib/sqlite/db.sqlite?_fk=true",
|
||||
"hashers": {
|
||||
"argon2": {
|
||||
"iterations": 2,
|
||||
"key_length": 16,
|
||||
"memory": 131072,
|
||||
"parallelism": 1,
|
||||
"salt_length": 16
|
||||
}
|
||||
},
|
||||
"identity": {
|
||||
"default_schema_url": "file:///etc/config/kratos/identity.schema.json"
|
||||
},
|
||||
"log": {
|
||||
"format": "text",
|
||||
"leak_sensitive_values": true,
|
||||
"level": "debug"
|
||||
},
|
||||
"secrets": {
|
||||
"cookie": ["PLEASE-CHANGE-ME-I-AM-VERY-INSECURE"]
|
||||
},
|
||||
"selfservice": {
|
||||
"default_browser_return_url": "http://127.0.0.1:4455/",
|
||||
"flows": {
|
||||
"error": {
|
||||
"ui_url": "http://127.0.0.1:4455/error"
|
||||
},
|
||||
"login": {
|
||||
"lifespan": "10m",
|
||||
"ui_url": "http://127.0.0.1:4455/auth/login"
|
||||
},
|
||||
"logout": {
|
||||
"after": {
|
||||
"default_browser_return_url": "http://127.0.0.1:4455/auth/login"
|
||||
}
|
||||
},
|
||||
"recovery": {
|
||||
"enabled": true,
|
||||
"lifespan": "1h",
|
||||
"ui_url": "http://127.0.0.1:4455/recovery"
|
||||
},
|
||||
"registration": {
|
||||
"after": {
|
||||
"password": {
|
||||
"hooks": [
|
||||
{
|
||||
"hook": "session"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"lifespan": "10m",
|
||||
"ui_url": "http://127.0.0.1:4455/auth/registration"
|
||||
},
|
||||
"settings": {
|
||||
"lifespan": "1h",
|
||||
"privileged_session_max_age": "15m",
|
||||
"ui_url": "http://127.0.0.1:4455/settings"
|
||||
},
|
||||
"verification": {
|
||||
"after": {
|
||||
"default_browser_return_url": "http://127.0.0.1:4455/"
|
||||
},
|
||||
"enabled": true,
|
||||
"lifespan": "1h",
|
||||
"ui_url": "http://127.0.0.1:4455/verify"
|
||||
}
|
||||
},
|
||||
"methods": {
|
||||
"link": {
|
||||
"enabled": true
|
||||
},
|
||||
"oidc": {
|
||||
"enabled": true,
|
||||
"config": {
|
||||
"providers": [
|
||||
{
|
||||
"id": "google",
|
||||
"provider": "google",
|
||||
"mapper_url": "file:///etc/config/kratos/oidc.google.jsonnet",
|
||||
"client_id": "client@example.com",
|
||||
"client_secret": "secret"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"password": {
|
||||
"enabled": true
|
||||
},
|
||||
"profile": {
|
||||
"enabled": true
|
||||
}
|
||||
},
|
||||
"whitelisted_return_urls": ["http://127.0.0.1:4455"]
|
||||
},
|
||||
"serve": {
|
||||
"admin": {
|
||||
"base_url": "http://kratos:4434/",
|
||||
"host": "0.0.0.0",
|
||||
"port": 4434
|
||||
},
|
||||
"public": {
|
||||
"base_url": "http://127.0.0.1:4433/",
|
||||
"cors": {
|
||||
"allow_credentials": true,
|
||||
"allowed_headers": ["Authorization", "Content-Type", "X-Session-Token"],
|
||||
"allowed_methods": ["POST", "GET", "PUT", "PATCH", "DELETE"],
|
||||
"allowed_origins": ["*"],
|
||||
"debug": false,
|
||||
"enabled": true,
|
||||
"exposed_headers": ["Content-Type"],
|
||||
"max_age": 0,
|
||||
"options_passthrough": false
|
||||
},
|
||||
"host": "0.0.0.0",
|
||||
"port": 4433
|
||||
}
|
||||
},
|
||||
"session": {
|
||||
"cookie": {
|
||||
"path": "/",
|
||||
"persistent": true,
|
||||
"same_site": "Lax"
|
||||
},
|
||||
"lifespan": "24h"
|
||||
},
|
||||
"version": "v0.5.3-alpha.1"
|
||||
}
|
||||
76
oryx/configx/stub/kratos/kratos.yaml
Normal file
76
oryx/configx/stub/kratos/kratos.yaml
Normal file
@@ -0,0 +1,76 @@
|
||||
version: v0.5.3-alpha.1
|
||||
|
||||
dsn: memory
|
||||
|
||||
serve:
|
||||
public:
|
||||
base_url: http://127.0.0.1:4433/
|
||||
cors:
|
||||
enabled: true
|
||||
admin:
|
||||
base_url: http://kratos:4434/
|
||||
|
||||
selfservice:
|
||||
default_browser_return_url: http://127.0.0.1:4455/
|
||||
whitelisted_return_urls:
|
||||
- http://127.0.0.1:4455
|
||||
|
||||
methods:
|
||||
password:
|
||||
enabled: true
|
||||
oidc:
|
||||
enabled: true
|
||||
|
||||
flows:
|
||||
error:
|
||||
ui_url: http://127.0.0.1:4455/error
|
||||
|
||||
settings:
|
||||
ui_url: http://127.0.0.1:4455/settings
|
||||
privileged_session_max_age: 15m
|
||||
|
||||
recovery:
|
||||
enabled: true
|
||||
ui_url: http://127.0.0.1:4455/recovery
|
||||
|
||||
verification:
|
||||
enabled: true
|
||||
ui_url: http://127.0.0.1:4455/verify
|
||||
after:
|
||||
default_browser_return_url: http://127.0.0.1:4455/
|
||||
|
||||
logout:
|
||||
after:
|
||||
default_browser_return_url: http://127.0.0.1:4455/auth/login
|
||||
|
||||
login:
|
||||
ui_url: http://127.0.0.1:4455/auth/login
|
||||
lifespan: 10m
|
||||
|
||||
registration:
|
||||
lifespan: 10m
|
||||
ui_url: http://127.0.0.1:4455/auth/registration
|
||||
|
||||
log:
|
||||
level: debug
|
||||
format: text
|
||||
leak_sensitive_values: true
|
||||
|
||||
secrets:
|
||||
cookie:
|
||||
- PLEASE-CHANGE-ME-I-AM-VERY-INSECURE
|
||||
|
||||
hashers:
|
||||
argon2:
|
||||
parallelism: 1
|
||||
memory: 131072
|
||||
iterations: 2
|
||||
salt_length: 16
|
||||
key_length: 16
|
||||
|
||||
identity:
|
||||
default_schema_url: file:///etc/config/kratos/identity.schema.json
|
||||
|
||||
courier:
|
||||
smtp:
|
||||
connection_uri: smtps://test:test@mailslurper:1025/?skip_ssl_verify=true
|
||||
27
oryx/configx/stub/multi/a.yaml
Normal file
27
oryx/configx/stub/multi/a.yaml
Normal file
@@ -0,0 +1,27 @@
|
||||
version: v0.5.3-alpha.1
|
||||
|
||||
dsn: memory
|
||||
|
||||
serve:
|
||||
public:
|
||||
base_url: http://127.0.0.1:4433/
|
||||
cors:
|
||||
enabled: true
|
||||
admin:
|
||||
base_url: http://kratos:4434/
|
||||
|
||||
selfservice:
|
||||
default_browser_return_url: http://127.0.0.1:4455/
|
||||
whitelisted_return_urls:
|
||||
- http://127.0.0.1:4455
|
||||
|
||||
methods:
|
||||
password:
|
||||
enabled: true
|
||||
|
||||
flows:
|
||||
error:
|
||||
ui_url: http://127.0.0.1:4455/error
|
||||
|
||||
settings:
|
||||
ui_url: http://127.0.0.1:4455/settings
|
||||
54
oryx/configx/stub/multi/b.yaml
Normal file
54
oryx/configx/stub/multi/b.yaml
Normal file
@@ -0,0 +1,54 @@
|
||||
selfservice:
|
||||
flows:
|
||||
settings:
|
||||
privileged_session_max_age: 15m
|
||||
|
||||
recovery:
|
||||
enabled: true
|
||||
ui_url: http://127.0.0.1:4455/recovery
|
||||
|
||||
verification:
|
||||
enabled: true
|
||||
ui_url: http://127.0.0.1:4455/verify
|
||||
after:
|
||||
default_browser_return_url: http://127.0.0.1:4455/
|
||||
|
||||
logout:
|
||||
after:
|
||||
default_browser_return_url: http://127.0.0.1:4455/auth/login
|
||||
|
||||
login:
|
||||
ui_url: http://127.0.0.1:4455/auth/login
|
||||
lifespan: 10m
|
||||
|
||||
registration:
|
||||
lifespan: 10m
|
||||
ui_url: http://127.0.0.1:4455/auth/registration
|
||||
after:
|
||||
password:
|
||||
hooks:
|
||||
- hook: session
|
||||
|
||||
log:
|
||||
level: debug
|
||||
format: text
|
||||
leak_sensitive_values: true
|
||||
|
||||
secrets:
|
||||
cookie:
|
||||
- PLEASE-CHANGE-ME-I-AM-VERY-INSECURE
|
||||
|
||||
hashers:
|
||||
argon2:
|
||||
parallelism: 1
|
||||
memory: 131072
|
||||
iterations: 2
|
||||
salt_length: 16
|
||||
key_length: 16
|
||||
|
||||
identity:
|
||||
default_schema_url: file:///etc/config/kratos/identity.schema.json
|
||||
|
||||
courier:
|
||||
smtp:
|
||||
connection_uri: smtps://test:test@mailslurper:1025/?skip_ssl_verify=true
|
||||
1085
oryx/configx/stub/multi/config.schema.json
Normal file
1085
oryx/configx/stub/multi/config.schema.json
Normal file
File diff suppressed because it is too large
Load Diff
124
oryx/configx/stub/multi/expected.json
Normal file
124
oryx/configx/stub/multi/expected.json
Normal file
@@ -0,0 +1,124 @@
|
||||
{
|
||||
"courier": {
|
||||
"smtp": {
|
||||
"connection_uri": "smtps://test:test@mailslurper:1025/?skip_ssl_verify=true",
|
||||
"from_address": "no-reply@ory.kratos.sh"
|
||||
}
|
||||
},
|
||||
"dsn": "sqlite:///var/lib/sqlite/db.sqlite?_fk=true",
|
||||
"hashers": {
|
||||
"argon2": {
|
||||
"iterations": 2,
|
||||
"key_length": 16,
|
||||
"memory": 131072,
|
||||
"parallelism": 1,
|
||||
"salt_length": 16
|
||||
}
|
||||
},
|
||||
"identity": {
|
||||
"default_schema_url": "file:///etc/config/kratos/identity.schema.json"
|
||||
},
|
||||
"log": {
|
||||
"format": "text",
|
||||
"leak_sensitive_values": true,
|
||||
"level": "debug"
|
||||
},
|
||||
"secrets": {
|
||||
"cookie": ["PLEASE-CHANGE-ME-I-AM-VERY-INSECURE"]
|
||||
},
|
||||
"selfservice": {
|
||||
"default_browser_return_url": "http://127.0.0.1:4455/",
|
||||
"flows": {
|
||||
"error": {
|
||||
"ui_url": "http://127.0.0.1:4455/error"
|
||||
},
|
||||
"login": {
|
||||
"lifespan": "10m",
|
||||
"ui_url": "http://127.0.0.1:4455/auth/login"
|
||||
},
|
||||
"logout": {
|
||||
"after": {
|
||||
"default_browser_return_url": "http://127.0.0.1:4455/auth/login"
|
||||
}
|
||||
},
|
||||
"recovery": {
|
||||
"enabled": true,
|
||||
"lifespan": "1h",
|
||||
"ui_url": "http://127.0.0.1:4455/recovery"
|
||||
},
|
||||
"registration": {
|
||||
"after": {
|
||||
"password": {
|
||||
"hooks": [
|
||||
{
|
||||
"hook": "session"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"lifespan": "10m",
|
||||
"ui_url": "http://127.0.0.1:4455/auth/registration"
|
||||
},
|
||||
"settings": {
|
||||
"lifespan": "1h",
|
||||
"privileged_session_max_age": "15m",
|
||||
"ui_url": "http://127.0.0.1:4455/settings"
|
||||
},
|
||||
"verification": {
|
||||
"after": {
|
||||
"default_browser_return_url": "http://127.0.0.1:4455/"
|
||||
},
|
||||
"enabled": true,
|
||||
"lifespan": "1h",
|
||||
"ui_url": "http://127.0.0.1:4455/verify"
|
||||
}
|
||||
},
|
||||
"methods": {
|
||||
"link": {
|
||||
"enabled": true
|
||||
},
|
||||
"oidc": {
|
||||
"enabled": false
|
||||
},
|
||||
"password": {
|
||||
"enabled": true
|
||||
},
|
||||
"profile": {
|
||||
"enabled": true
|
||||
}
|
||||
},
|
||||
"whitelisted_return_urls": ["http://127.0.0.1:4455"]
|
||||
},
|
||||
"serve": {
|
||||
"admin": {
|
||||
"base_url": "http://kratos:4434/",
|
||||
"host": "0.0.0.0",
|
||||
"port": 4434
|
||||
},
|
||||
"public": {
|
||||
"base_url": "http://127.0.0.1:4433/",
|
||||
"cors": {
|
||||
"allow_credentials": true,
|
||||
"allowed_headers": ["Authorization", "Content-Type", "X-Session-Token"],
|
||||
"allowed_methods": ["POST", "GET", "PUT", "PATCH", "DELETE"],
|
||||
"allowed_origins": ["*"],
|
||||
"debug": false,
|
||||
"enabled": true,
|
||||
"exposed_headers": ["Content-Type"],
|
||||
"max_age": 0,
|
||||
"options_passthrough": false
|
||||
},
|
||||
"host": "0.0.0.0",
|
||||
"port": 4433
|
||||
}
|
||||
},
|
||||
"session": {
|
||||
"cookie": {
|
||||
"path": "/",
|
||||
"persistent": true,
|
||||
"same_site": "Lax"
|
||||
},
|
||||
"lifespan": "24h"
|
||||
},
|
||||
"version": "v0.5.3-alpha.1"
|
||||
}
|
||||
105
oryx/configx/stub/nested-array/config.schema.json
Normal file
105
oryx/configx/stub/nested-array/config.schema.json
Normal file
@@ -0,0 +1,105 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"providers": {
|
||||
"title": "OpenID Connect and OAuth2 Providers",
|
||||
"description": "A list and configuration of OAuth2 and OpenID Connect providers ORY Kratos should integrate with.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"examples": ["google"]
|
||||
},
|
||||
"provider": {
|
||||
"title": "Provider",
|
||||
"description": "Can be one of github, gitlab, generic, google, microsoft, discord.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"github",
|
||||
"gitlab",
|
||||
"generic",
|
||||
"google",
|
||||
"microsoft",
|
||||
"discord"
|
||||
],
|
||||
"examples": ["google"]
|
||||
},
|
||||
"client_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"client_secret": {
|
||||
"type": "string"
|
||||
},
|
||||
"issuer_url": {
|
||||
"type": "string",
|
||||
"format": "uri",
|
||||
"examples": ["https://accounts.google.com"]
|
||||
},
|
||||
"auth_url": {
|
||||
"type": "string",
|
||||
"format": "uri",
|
||||
"examples": ["https://accounts.google.com/o/oauth2/v2/auth"]
|
||||
},
|
||||
"token_url": {
|
||||
"type": "string",
|
||||
"format": "uri",
|
||||
"examples": ["https://www.googleapis.com/oauth2/v4/token"]
|
||||
},
|
||||
"mapper_url": {
|
||||
"title": "Jsonnet Mapper URL",
|
||||
"description": "The URL where the jsonnet source is located for mapping the provider's data to ORY Kratos data.",
|
||||
"type": "string",
|
||||
"format": "uri",
|
||||
"examples": [
|
||||
"file://path/to/oidc.jsonnet",
|
||||
"https://foo.bar.com/path/to/oidc.jsonnet",
|
||||
"base64://bG9jYWwgc3ViamVjdCA9I..."
|
||||
]
|
||||
},
|
||||
"scope": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"examples": ["offline_access", "profile"]
|
||||
}
|
||||
},
|
||||
"tenant": {
|
||||
"title": "Azure AD Tenant",
|
||||
"description": "The Azure AD Tenant to use for authentication.",
|
||||
"type": "string",
|
||||
"examples": [
|
||||
"common",
|
||||
"organizations",
|
||||
"consumers",
|
||||
"8eaef023-2b34-4da1-9baa-8bc8c9d6a490",
|
||||
"contoso.onmicrosoft.com"
|
||||
]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": [],
|
||||
"if": {
|
||||
"properties": {
|
||||
"provider": {
|
||||
"const": "microsoft"
|
||||
}
|
||||
},
|
||||
"required": ["provider"]
|
||||
},
|
||||
"then": {
|
||||
"required": ["tenant"]
|
||||
},
|
||||
"else": {
|
||||
"not": {
|
||||
"properties": {
|
||||
"tenant": {}
|
||||
},
|
||||
"required": ["tenant"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
11
oryx/configx/stub/nested-array/expected.json
Normal file
11
oryx/configx/stub/nested-array/expected.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"providers": [
|
||||
{
|
||||
"id": "google",
|
||||
"client_id": "client@example.com"
|
||||
},
|
||||
{
|
||||
"client_id": "some@example.com"
|
||||
}
|
||||
]
|
||||
}
|
||||
2
oryx/configx/stub/nested-array/kratos.yaml
Normal file
2
oryx/configx/stub/nested-array/kratos.yaml
Normal file
@@ -0,0 +1,2 @@
|
||||
providers:
|
||||
- id: google
|
||||
19
oryx/configx/stub/watch/config.schema.json
Normal file
19
oryx/configx/stub/watch/config.schema.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"$id": "https://example.com/config.schema.json",
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "config",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"dsn": {
|
||||
"type": "string"
|
||||
},
|
||||
"foo": {
|
||||
"const": "bar"
|
||||
},
|
||||
"bar": {
|
||||
"type": "string",
|
||||
"enum": ["foo", "bar", "baz"]
|
||||
}
|
||||
},
|
||||
"required": ["dsn"]
|
||||
}
|
||||
68
oryx/configx/tls.schema.json
Normal file
68
oryx/configx/tls.schema.json
Normal file
@@ -0,0 +1,68 @@
|
||||
{
|
||||
"$id": "https://github.com/ory/x/tlsx/config.schema.json",
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "HTTPS",
|
||||
"description": "Configure HTTP over TLS (HTTPS).",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"key": {
|
||||
"title": "Private Key (PEM)",
|
||||
"$ref": "#/definitions/source"
|
||||
},
|
||||
"cert": {
|
||||
"title": "TLS Certificate (PEM)",
|
||||
"$ref": "#/definitions/source"
|
||||
},
|
||||
"allow_termination_from": {
|
||||
"type": "array",
|
||||
"description": "Allow-list one or multiple CIDR address ranges and allow them to terminate TLS connections. Be aware that the X-Forwarded-Proto header must be set and must never be modifiable by anyone but your proxy / gateway / load balancer. Supports ipv4 and ipv6. The service serves http instead of https when this option is set.",
|
||||
"items": {
|
||||
"description": "CIDR address range.",
|
||||
"type": "string",
|
||||
"oneOf": [
|
||||
{
|
||||
"pattern": "^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))/([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8])$"
|
||||
},
|
||||
{
|
||||
"pattern": "^([0-9]{1,3}\\.){3}[0-9]{1,3}/([0-9]|[1-2][0-9]|3[0-2])$"
|
||||
}
|
||||
],
|
||||
"examples": ["127.0.0.1/32"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
"source": {
|
||||
"type": "object",
|
||||
"oneOf": [
|
||||
{
|
||||
"properties": {
|
||||
"path": {
|
||||
"title": "Path to PEM-encoded File",
|
||||
"type": "string",
|
||||
"examples": ["path/to/file.pem"]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"base64": {
|
||||
"title": "Base64 Encoded Inline",
|
||||
"description": "The base64 string of the PEM-encoded file content. Can be generated using for example `base64 -i path/to/file.pem`.",
|
||||
"type": "string",
|
||||
"examples": [
|
||||
"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tXG5NSUlEWlRDQ0FrMmdBd0lCQWdJRVY1eE90REFOQmdr..."
|
||||
]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user