mirror of
https://github.com/tinyauthapp/tinyauth.git
synced 2026-09-24 11:43:32 +08:00
Compare commits
7
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fcc9125279 | ||
|
|
fb86f443a1 | ||
|
|
314758bf68 | ||
|
|
b29e1bddc5 | ||
|
|
f98c844f28 | ||
|
|
a62ef392e2 | ||
|
|
261d882af3 |
@@ -33,6 +33,9 @@ jobs:
|
||||
with:
|
||||
sqlc-version: "1.31.1"
|
||||
|
||||
- name: Setup controller-gen
|
||||
run: go install sigs.k8s.io/controller-tools/cmd/controller-gen@v0.22.0
|
||||
|
||||
- name: Check codegen is up to date
|
||||
run: |
|
||||
sqlc generate
|
||||
|
||||
@@ -19,7 +19,12 @@ PROD_COMPOSE := $(shell test -f "docker-compose.test.prod.yml" && echo "docker-c
|
||||
|
||||
.DEFAULT_GOAL := binary
|
||||
|
||||
.PHONY: deps clean-data clean-webui webui binary binary-linux-amd64 binary-linux-arm64 test vet test-race dev dev-infisical prod prod-infisical sql generate docker docker-distroless
|
||||
.PHONY: deps clean-data clean-webui webui binary binary-linux-amd64 binary-linux-arm64 test vet test-race dev dev-infisical prod prod-infisical sql generate docker docker-distroless tools
|
||||
|
||||
# Tools
|
||||
tools:
|
||||
go install sigs.k8s.io/controller-tools/cmd/controller-gen@v0.22.0
|
||||
go install github.com/sqlc-dev/sqlc/cmd/sqlc@v1.31.1
|
||||
|
||||
# Deps
|
||||
deps:
|
||||
|
||||
@@ -70,6 +70,7 @@ require (
|
||||
github.com/docker/go-connections v0.6.0 // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.13.0 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.9.1 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.12 // indirect
|
||||
@@ -77,6 +78,20 @@ require (
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.8 // indirect
|
||||
github.com/go-logr/logr v1.4.4 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-openapi/jsonpointer v1.0.0 // indirect
|
||||
github.com/go-openapi/jsonreference v1.0.0 // indirect
|
||||
github.com/go-openapi/swag v0.27.1 // indirect
|
||||
github.com/go-openapi/swag/cmdutils v0.27.1 // indirect
|
||||
github.com/go-openapi/swag/conv v0.27.1 // indirect
|
||||
github.com/go-openapi/swag/fileutils v0.27.1 // indirect
|
||||
github.com/go-openapi/swag/jsonutils v0.27.1 // indirect
|
||||
github.com/go-openapi/swag/loading v0.27.1 // indirect
|
||||
github.com/go-openapi/swag/mangling v0.27.1 // indirect
|
||||
github.com/go-openapi/swag/netutils v0.27.1 // indirect
|
||||
github.com/go-openapi/swag/pools v0.27.1 // indirect
|
||||
github.com/go-openapi/swag/stringutils v0.27.1 // indirect
|
||||
github.com/go-openapi/swag/typeutils v0.27.1 // indirect
|
||||
github.com/go-openapi/swag/yamlutils v0.27.1 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.30.1 // indirect
|
||||
@@ -142,6 +157,7 @@ require (
|
||||
golang.org/x/text v0.41.0 // indirect
|
||||
golang.org/x/time v0.15.0 // indirect
|
||||
google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af // indirect
|
||||
gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gotest.tools/v3 v3.5.2 // indirect
|
||||
k8s.io/klog/v2 v2.140.0 // indirect
|
||||
|
||||
@@ -138,6 +138,8 @@ github.com/go-openapi/swag/fileutils v0.27.1 h1:QQqBSoi5mW4XpU85nS0mLcA+zAE6vLzr
|
||||
github.com/go-openapi/swag/fileutils v0.27.1/go.mod h1:VvJFZLTZS0AI854gEQz5tk7dBESdLjiNUMSZ/th2ry8=
|
||||
github.com/go-openapi/swag/jsonutils v0.27.1 h1:SVgK3i4USzCU5mibOOS/l4ea2h9UQXy7J7RNLTjuXjU=
|
||||
github.com/go-openapi/swag/jsonutils v0.27.1/go.mod h1:tdlEpZqdcQ17uj6J4YdK9vd8It5qWMwjWXOs0tjpRlk=
|
||||
github.com/go-openapi/swag/jsonutils/fixtures_test v0.27.1 h1:mJu3COL9WEaZVp/Kf2PRMi7tPszPEJfSr/OO75ynCs8=
|
||||
github.com/go-openapi/swag/jsonutils/fixtures_test v0.27.1/go.mod h1:mofwUWx70wvskwESqRJ//k/9kURmCgyJl5m5Ppoh5kY=
|
||||
github.com/go-openapi/swag/loading v0.27.1 h1:/DxUgDXKbBX4bcn7r9uEXfJyzN5XpiJmZplzQTjrRCY=
|
||||
github.com/go-openapi/swag/loading v0.27.1/go.mod h1:jvGh3iA2+zyUUycB5fgJWzeHnhrpvGnJJM0RVE9ZShE=
|
||||
github.com/go-openapi/swag/mangling v0.27.1 h1:yC9D0HyUE8gbP+BfmGx9+AA89ikwZTMjESK3OnnoaqA=
|
||||
@@ -152,6 +154,10 @@ github.com/go-openapi/swag/typeutils v0.27.1 h1:KSTdFlfnse4r6dP9IrEnwMldjE+zs71U
|
||||
github.com/go-openapi/swag/typeutils v0.27.1/go.mod h1:Srm0xFNRZ1Y+vCxJclo5qzx8aj+1pAKda/YfFPrG0dQ=
|
||||
github.com/go-openapi/swag/yamlutils v0.27.1 h1:ftxv6xvXb1E3zohUc+okZ9nSqNb9StQX/FXnKZ98sQA=
|
||||
github.com/go-openapi/swag/yamlutils v0.27.1/go.mod h1:bnxFIB1qewGRiZHypXGZ3fNgf13/0HfRgnS/iZBDrOo=
|
||||
github.com/go-openapi/testify/enable/yaml/v2 v2.6.0 h1:gGHwAJ0R/5jU8BEGDbfRNR3hL68dAVi84WuOApp29B0=
|
||||
github.com/go-openapi/testify/enable/yaml/v2 v2.6.0/go.mod h1:tY+St1SGq4NFl0QIqdTY4aEdbChAHxhyB77XQi9iJCo=
|
||||
github.com/go-openapi/testify/v2 v2.6.0 h1:5PKH2HE7YJ/LuRPQGvSxBRlFXNQhSetBLlGAgUEu3ug=
|
||||
github.com/go-openapi/testify/v2 v2.6.0/go.mod h1:SgsVHtfooshd0tublTtJ50FPKhujf47YRqauXXOUxfw=
|
||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||
|
||||
@@ -12,12 +12,8 @@ import (
|
||||
"go.uber.org/dig"
|
||||
)
|
||||
|
||||
// LabelProvider looks up the apps it knows about for the given domain. A
|
||||
// provider that knows which hosts its apps are served on MUST only yield the
|
||||
// ones that are actually served on domain, so that an unrelated app cannot
|
||||
// claim it by name.
|
||||
type LabelProvider interface {
|
||||
Lookup(domain string, locator func(name string, app *model.App) bool) error
|
||||
Lookup(locator func(name string, app *model.App) bool) error
|
||||
}
|
||||
|
||||
type AccessControlsService struct {
|
||||
@@ -150,7 +146,7 @@ func (service *AccessControlsService) GetAccessControls(domain string) (*model.A
|
||||
// If we have a label provider configured, try to get ACLs from it
|
||||
if service.labelProvider != nil {
|
||||
return service.getACLs(domain, func(locator func(name string, app *model.App) bool) error {
|
||||
return service.labelProvider.Lookup(domain, locator)
|
||||
return service.labelProvider.Lookup(locator)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ func newMockProvider(acls map[string]model.App, shouldError bool) *mockProvider
|
||||
return &mockProvider{acls: acls, shouldError: shouldError}
|
||||
}
|
||||
|
||||
func (m *mockProvider) Lookup(_ string, locator func(name string, app *model.App) bool) error {
|
||||
func (m *mockProvider) Lookup(locator func(name string, app *model.App) bool) error {
|
||||
if m.shouldError {
|
||||
return errors.New("mock error")
|
||||
}
|
||||
@@ -153,9 +153,7 @@ func TestAccessControlsService(t *testing.T) {
|
||||
Config: &model.Config{},
|
||||
LabelProvider: mock,
|
||||
})
|
||||
app, err := acls.getACLs(test.domain, func(locator func(name string, app *model.App) bool) error {
|
||||
return mock.Lookup(test.domain, locator)
|
||||
})
|
||||
app, err := acls.getACLs(test.domain, mock.Lookup)
|
||||
if test.errorFunc != nil {
|
||||
test.errorFunc(t, err)
|
||||
return
|
||||
@@ -193,9 +191,7 @@ func TestAccessControlsService(t *testing.T) {
|
||||
Config: &model.Config{},
|
||||
LabelProvider: mock,
|
||||
})
|
||||
_, err := acls.getACLs("example.com", func(locator func(name string, app *model.App) bool) error {
|
||||
return mock.Lookup("example.com", locator)
|
||||
})
|
||||
_, err := acls.getACLs("example.com", mock.Lookup)
|
||||
assert.Error(t, err)
|
||||
|
||||
// get acls should return an error when multiple apps with the same domain exist
|
||||
|
||||
@@ -119,10 +119,7 @@ func (docker *DockerService) inspectContainer(containerId string) (container.Ins
|
||||
return docker.client.ContainerInspect(docker.context, containerId)
|
||||
}
|
||||
|
||||
// Lookup yields every app labelled on a running container. Container labels
|
||||
// carry no routing information, so the domain cannot be used to narrow the
|
||||
// results down and the caller is left to match them.
|
||||
func (docker *DockerService) Lookup(_ string, locator func(name string, app *model.App) bool) error {
|
||||
func (docker *DockerService) Lookup(locator func(name string, app *model.App) bool) error {
|
||||
if !docker.isConnected {
|
||||
docker.log.App.Debug().Msg("Docker service not connected, returning empty labels")
|
||||
return nil
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/tinyauthapp/tinyauth/internal/model"
|
||||
"github.com/tinyauthapp/tinyauth/internal/utils/logger"
|
||||
"github.com/tinyauthapp/tinyauth/pkg/apis/tinyauth/v1alpha1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
)
|
||||
|
||||
type KubernetesCRDInput struct {
|
||||
Log *logger.Logger
|
||||
Client kubernetes.Interface
|
||||
}
|
||||
|
||||
type KubernetesCRDExtractor struct {
|
||||
log *logger.Logger
|
||||
client kubernetes.Interface
|
||||
}
|
||||
|
||||
func NewKubernetesCRDExtractor(i KubernetesCRDInput) *KubernetesCRDExtractor {
|
||||
return &KubernetesCRDExtractor{
|
||||
log: i.Log,
|
||||
client: i.Client,
|
||||
}
|
||||
}
|
||||
|
||||
func (k *KubernetesCRDExtractor) Extract(app *v1alpha1.Application) ExtractionResult {
|
||||
meta := &ResourceMeta{
|
||||
Typ: ResourceTypeCRD,
|
||||
Name: app.GetName(),
|
||||
Namespace: app.GetNamespace(),
|
||||
}
|
||||
|
||||
if !ensureResourceMeta(meta) {
|
||||
k.log.App.Warn().Str("namespace", meta.Namespace).Str("name", meta.Name).Msg("Resource has no namespace or name, skipping")
|
||||
return ExtractionResult{}
|
||||
}
|
||||
|
||||
if app.Spec.Config.Domain == "" {
|
||||
k.log.App.Warn().Str("name", meta.Name).Str("namespace", meta.Namespace).Msg("Application has no domain, skipping")
|
||||
return ExtractionResult{
|
||||
Meta: meta,
|
||||
Apps: nil,
|
||||
}
|
||||
}
|
||||
|
||||
if !ensureAscii(app.Spec.Config.Domain) {
|
||||
k.log.App.Warn().Str("name", meta.Name).Str("namespace", meta.Namespace).Str("domain", app.Spec.Config.Domain).Msg("Domain is invalid, skipping")
|
||||
return ExtractionResult{
|
||||
Meta: meta,
|
||||
Apps: nil,
|
||||
}
|
||||
}
|
||||
|
||||
// Convert the CRD to the internal representation.
|
||||
internalApp := app.Spec.ToInternalApp()
|
||||
passwordRef := app.Spec.Response.BasicAuth.PasswordSecretRef
|
||||
if passwordRef != nil {
|
||||
secret, err := k.client.CoreV1().Secrets(meta.Namespace).Get(context.Background(), passwordRef.Name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
k.log.App.Warn().Err(err).Str("namespace", meta.Namespace).Str("name", meta.Name).Str("secret", passwordRef.Name).Str("key", passwordRef.Key).Msg("Failed to read basic auth password Secret, skipping")
|
||||
return ExtractionResult{Meta: meta}
|
||||
}
|
||||
|
||||
password, ok := secret.Data[passwordRef.Key]
|
||||
if !ok {
|
||||
k.log.App.Warn().Str("namespace", meta.Namespace).Str("name", meta.Name).Str("secret", passwordRef.Name).Str("key", passwordRef.Key).Msg("Basic auth password Secret key does not exist, skipping")
|
||||
return ExtractionResult{Meta: meta}
|
||||
}
|
||||
|
||||
internalApp.Response.BasicAuth.Password = string(password)
|
||||
}
|
||||
|
||||
return ExtractionResult{
|
||||
Meta: meta,
|
||||
Apps: map[string]model.App{
|
||||
meta.Name: internalApp,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -2,11 +2,31 @@ package service
|
||||
|
||||
import (
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/tinyauthapp/tinyauth/internal/model"
|
||||
"github.com/tinyauthapp/tinyauth/internal/utils/decoders"
|
||||
"github.com/tinyauthapp/tinyauth/internal/utils/logger"
|
||||
networking "k8s.io/api/networking/v1"
|
||||
)
|
||||
|
||||
func hostMatchesHostname(host string, hostname string) bool {
|
||||
host = normalizeDomain(host)
|
||||
hostname = normalizeDomain(hostname)
|
||||
if suffix, ok := strings.CutPrefix(host, "*."); ok {
|
||||
return strings.HasSuffix(hostname, "."+suffix)
|
||||
}
|
||||
return host == hostname
|
||||
}
|
||||
|
||||
func hostCoversName(host string, name string) bool {
|
||||
host = strings.ToLower(host)
|
||||
if strings.HasPrefix(host, "*.") {
|
||||
return true
|
||||
}
|
||||
return strings.HasPrefix(host, strings.ToLower(name+"."))
|
||||
}
|
||||
|
||||
type KubernetesIngressExtractor struct {
|
||||
log *logger.Logger
|
||||
}
|
||||
@@ -54,15 +74,61 @@ func (k *KubernetesIngressExtractor) getHosts(rules []networking.IngressRule) []
|
||||
return hosts
|
||||
}
|
||||
|
||||
func (k *KubernetesIngressExtractor) Extract(ingress *networking.Ingress) *ExtractionResult {
|
||||
func (k *KubernetesIngressExtractor) Extract(ingress *networking.Ingress) ExtractionResult {
|
||||
meta := &ResourceMeta{
|
||||
Typ: ResourceTypeIngress,
|
||||
Name: ingress.GetName(),
|
||||
Namespace: ingress.GetNamespace(),
|
||||
}
|
||||
|
||||
if !ensureResourceMeta(meta) {
|
||||
k.log.App.Warn().Str("namespace", meta.Namespace).Str("name", meta.Name).Msg("Resource has no namespace or name, skipping")
|
||||
return ExtractionResult{}
|
||||
}
|
||||
|
||||
annotations := ingress.GetAnnotations()
|
||||
hosts := k.getHosts(ingress.Spec.Rules)
|
||||
|
||||
return &ExtractionResult{
|
||||
typ: ResourceTypeIngress,
|
||||
name: ingress.GetName(),
|
||||
namespace: ingress.GetNamespace(),
|
||||
hosts: hosts,
|
||||
annotations: annotations,
|
||||
if len(hosts) == 0 {
|
||||
k.log.App.Warn().Str("namespace", meta.Namespace).Str("name", meta.Name).Msg("No hosts found in resource, skipping")
|
||||
return ExtractionResult{
|
||||
Meta: meta,
|
||||
}
|
||||
}
|
||||
|
||||
labels, err := decoders.DecodeLabels[model.Apps](annotations, "apps")
|
||||
if err != nil {
|
||||
k.log.App.Warn().Err(err).Str("namespace", meta.Namespace).Str("name", meta.Name).Msg("Failed to decode resource labels, skipping")
|
||||
return ExtractionResult{
|
||||
Meta: meta,
|
||||
}
|
||||
}
|
||||
|
||||
apps := make(map[string]model.App)
|
||||
|
||||
for name, config := range labels.Apps {
|
||||
if config.Config.Domain != "" {
|
||||
if !ensureAscii(config.Config.Domain) {
|
||||
k.log.App.Warn().Err(err).Str("namespace", meta.Namespace).Str("name", meta.Name).Str("domain", config.Config.Domain).Msg("Domain is invalid, matching will rely on app name")
|
||||
} else {
|
||||
if slices.ContainsFunc(hosts, func(host string) bool {
|
||||
return hostMatchesHostname(host, config.Config.Domain)
|
||||
}) {
|
||||
apps[name] = config
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if slices.ContainsFunc(hosts, func(host string) bool {
|
||||
return hostCoversName(host, name)
|
||||
}) {
|
||||
apps[name] = config
|
||||
}
|
||||
}
|
||||
|
||||
return ExtractionResult{
|
||||
Meta: meta,
|
||||
Apps: apps,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,15 +3,13 @@ package service
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/steveiliop56/ding"
|
||||
"github.com/tinyauthapp/tinyauth/internal/model"
|
||||
"github.com/tinyauthapp/tinyauth/internal/utils/decoders"
|
||||
"github.com/tinyauthapp/tinyauth/internal/utils/logger"
|
||||
"github.com/tinyauthapp/tinyauth/pkg/apis/tinyauth/v1alpha1"
|
||||
"go.uber.org/dig"
|
||||
networking "k8s.io/api/networking/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
@@ -20,6 +18,7 @@ import (
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
"k8s.io/client-go/dynamic"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/rest"
|
||||
)
|
||||
|
||||
@@ -32,10 +31,26 @@ func (w watchedResource) pretty() string {
|
||||
return w.gvr.Group + "/" + w.gvr.Version + "/" + w.gvr.Resource
|
||||
}
|
||||
|
||||
func ensureResourceMeta(meta *ResourceMeta) bool {
|
||||
return meta.Name != "" && meta.Namespace != ""
|
||||
}
|
||||
|
||||
type ResourceMeta struct {
|
||||
Typ ResourceType
|
||||
Name string
|
||||
Namespace string
|
||||
}
|
||||
|
||||
type ExtractionResult struct {
|
||||
Meta *ResourceMeta
|
||||
Apps map[string]model.App
|
||||
}
|
||||
|
||||
type ResourceType string
|
||||
|
||||
const (
|
||||
ResourceTypeIngress ResourceType = "ingress"
|
||||
ResourceTypeCRD ResourceType = "crd"
|
||||
)
|
||||
|
||||
var supportedResources = []watchedResource{
|
||||
@@ -47,36 +62,19 @@ var supportedResources = []watchedResource{
|
||||
},
|
||||
typ: ResourceTypeIngress,
|
||||
},
|
||||
}
|
||||
|
||||
func hostMatchesHostname(host string, hostname string) bool {
|
||||
host = normalizeDomain(host)
|
||||
hostname = normalizeDomain(hostname)
|
||||
if suffix, ok := strings.CutPrefix(host, "*."); ok {
|
||||
return strings.HasSuffix(hostname, "."+suffix)
|
||||
}
|
||||
return host == hostname
|
||||
}
|
||||
|
||||
func hostCoversName(host string, name string) bool {
|
||||
host = strings.ToLower(host)
|
||||
if strings.HasPrefix(host, "*.") {
|
||||
return true
|
||||
}
|
||||
return strings.HasPrefix(host, strings.ToLower(name+"."))
|
||||
}
|
||||
|
||||
type ExtractionResult struct {
|
||||
typ ResourceType
|
||||
name string
|
||||
namespace string
|
||||
hosts []string
|
||||
annotations map[string]string
|
||||
{
|
||||
gvr: schema.GroupVersionResource{
|
||||
Group: "tinyauth.app",
|
||||
Version: "v1alpha1",
|
||||
Resource: "applications",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
type typedItem struct {
|
||||
typ ResourceType
|
||||
ingress *networking.Ingress
|
||||
crd *v1alpha1.Application
|
||||
}
|
||||
|
||||
func convertFromUnstructured[T any](obj *unstructured.Unstructured) (*T, error) {
|
||||
@@ -100,38 +98,28 @@ func (ti *typedItem) fromUnstructured(typ ResourceType, obj *unstructured.Unstru
|
||||
typ: ResourceTypeIngress,
|
||||
ingress: typed,
|
||||
}, nil
|
||||
case ResourceTypeCRD:
|
||||
typed, err := convertFromUnstructured[v1alpha1.Application](obj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &typedItem{
|
||||
typ: ResourceTypeCRD,
|
||||
crd: typed,
|
||||
}, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown resource type %s", typ)
|
||||
}
|
||||
}
|
||||
|
||||
type resourceEntry struct {
|
||||
name string
|
||||
app model.App
|
||||
}
|
||||
|
||||
type routedApps struct {
|
||||
hosts []string
|
||||
entries []resourceEntry
|
||||
}
|
||||
|
||||
type resourceKey struct {
|
||||
typ ResourceType
|
||||
namespace string
|
||||
name string
|
||||
}
|
||||
|
||||
type KubernetesService struct {
|
||||
log *logger.Logger
|
||||
|
||||
apps map[resourceKey]routedApps
|
||||
client dynamic.Interface
|
||||
mu sync.RWMutex
|
||||
connected bool
|
||||
|
||||
extractors struct {
|
||||
ingress *KubernetesIngressExtractor
|
||||
}
|
||||
apps map[ResourceMeta]map[string]model.App
|
||||
client dynamic.Interface
|
||||
typedClient kubernetes.Interface
|
||||
mu sync.RWMutex
|
||||
connected bool
|
||||
}
|
||||
|
||||
type KubernetesServiceInput struct {
|
||||
@@ -152,16 +140,17 @@ func NewKubernetesService(i KubernetesServiceInput) (*KubernetesService, error)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create kubernetes client: %w", err)
|
||||
}
|
||||
|
||||
service := &KubernetesService{
|
||||
log: i.Log,
|
||||
client: client,
|
||||
apps: make(map[resourceKey]routedApps),
|
||||
typedClient, err := kubernetes.NewForConfig(cfg)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create typed kubernetes client: %w", err)
|
||||
}
|
||||
|
||||
service.extractors.ingress = NewKubernetesIngressExtractor(KubernetesIngressExtractorInput{
|
||||
Log: i.Log,
|
||||
})
|
||||
service := &KubernetesService{
|
||||
log: i.Log,
|
||||
client: client,
|
||||
typedClient: typedClient,
|
||||
apps: make(map[ResourceMeta]map[string]model.App),
|
||||
}
|
||||
|
||||
watchedGVRs := make(map[string]bool)
|
||||
|
||||
@@ -171,8 +160,7 @@ func NewKubernetesService(i KubernetesServiceInput) (*KubernetesService, error)
|
||||
cancel()
|
||||
|
||||
if err != nil {
|
||||
// The Gateway API CRDs are not installed on every cluster, so a
|
||||
// single unreachable resource is not fatal
|
||||
// The CRD may not be available yet, so we'll fall back to ingress
|
||||
i.Log.App.Warn().Err(err).Str("res", res.pretty()).Msg("Failed to access resource, skipping watcher")
|
||||
continue
|
||||
}
|
||||
@@ -196,121 +184,77 @@ func NewKubernetesService(i KubernetesServiceInput) (*KubernetesService, error)
|
||||
return service, nil
|
||||
}
|
||||
|
||||
func (k *KubernetesService) addResourceEntries(key resourceKey, hosts []string, entries []resourceEntry) {
|
||||
func (k *KubernetesService) addResource(result ExtractionResult) {
|
||||
k.mu.Lock()
|
||||
defer k.mu.Unlock()
|
||||
k.apps[key] = routedApps{
|
||||
hosts: hosts,
|
||||
entries: entries,
|
||||
}
|
||||
k.apps[*result.Meta] = result.Apps
|
||||
}
|
||||
|
||||
func (k *KubernetesService) removeResource(key resourceKey) {
|
||||
func (k *KubernetesService) removeResource(meta ResourceMeta) {
|
||||
k.mu.Lock()
|
||||
defer k.mu.Unlock()
|
||||
delete(k.apps, key)
|
||||
delete(k.apps, meta)
|
||||
}
|
||||
|
||||
func (k *KubernetesService) getEntry(domain string, locator func(name string, app *model.App) bool) {
|
||||
if !ensureAscii(domain) {
|
||||
k.log.App.Debug().Str("domain", domain).Msg("Domain is invalid, skipping lookup")
|
||||
return
|
||||
}
|
||||
|
||||
func (k *KubernetesService) getEntry(locator func(name string, app *model.App) bool) {
|
||||
k.mu.RLock()
|
||||
defer k.mu.RUnlock()
|
||||
|
||||
// O(n^2) is not great but the number of resource entries is expected to be small
|
||||
for _, app := range k.apps {
|
||||
if !slices.ContainsFunc(app.hosts, func(host string) bool {
|
||||
return hostMatchesHostname(host, domain)
|
||||
}) {
|
||||
continue
|
||||
}
|
||||
for _, entry := range app.entries {
|
||||
if ok := locator(entry.name, &entry.app); ok {
|
||||
for _, apps := range k.apps {
|
||||
for name, app := range apps {
|
||||
if ok := locator(name, &app); ok {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (k *KubernetesService) updateFromItem(res watchedResource, typedItem *typedItem) {
|
||||
var result *ExtractionResult
|
||||
|
||||
func (k *KubernetesService) watchedItemChange(res watchedResource, typedItem *typedItem, event watch.EventType) {
|
||||
if typedItem == nil {
|
||||
k.log.App.Warn().Str("res", res.pretty()).Msg("Resource is nil, skipping")
|
||||
return
|
||||
}
|
||||
|
||||
var result ExtractionResult
|
||||
|
||||
switch typedItem.typ {
|
||||
case ResourceTypeIngress:
|
||||
if typedItem.ingress == nil {
|
||||
k.log.App.Warn().Str("res", res.pretty()).Msg("Ingress is nil, skipping")
|
||||
return
|
||||
}
|
||||
result = k.extractors.ingress.Extract(typedItem.ingress)
|
||||
extractor := NewKubernetesIngressExtractor(KubernetesIngressExtractorInput{
|
||||
Log: k.log,
|
||||
})
|
||||
result = extractor.Extract(typedItem.ingress)
|
||||
case ResourceTypeCRD:
|
||||
if typedItem.crd == nil {
|
||||
k.log.App.Warn().Str("res", res.pretty()).Msg("CRD is nil, skipping")
|
||||
return
|
||||
}
|
||||
extractor := NewKubernetesCRDExtractor(KubernetesCRDInput{
|
||||
Log: k.log,
|
||||
Client: k.typedClient,
|
||||
})
|
||||
result = extractor.Extract(typedItem.crd)
|
||||
}
|
||||
|
||||
if result == nil {
|
||||
if event == watch.Deleted {
|
||||
if result.Meta != nil {
|
||||
k.removeResource(*result.Meta)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if result.Apps == nil {
|
||||
k.log.App.Warn().Str("res", res.pretty()).Msg("Failed to extract resource, skipping")
|
||||
return
|
||||
}
|
||||
|
||||
key := resourceKey{
|
||||
typ: res.typ,
|
||||
namespace: result.namespace,
|
||||
name: result.name,
|
||||
}
|
||||
|
||||
if len(result.hosts) == 0 {
|
||||
k.log.App.Warn().Str("res", res.pretty()).Str("namespace", key.namespace).Str("name", key.name).Msg("No hosts found in resource, skipping")
|
||||
k.removeResource(key)
|
||||
return
|
||||
}
|
||||
|
||||
labels, err := decoders.DecodeLabels[model.Apps](result.annotations, "apps")
|
||||
if err != nil {
|
||||
k.log.App.Warn().Err(err).Str("namespace", key.namespace).Str("name", key.name).Msg("Failed to decode resource labels, skipping")
|
||||
k.removeResource(key)
|
||||
return
|
||||
}
|
||||
|
||||
var entries []resourceEntry
|
||||
|
||||
for name, config := range labels.Apps {
|
||||
if config.Config.Domain != "" {
|
||||
if !ensureAscii(config.Config.Domain) {
|
||||
k.log.App.Warn().Err(err).Str("namespace", key.namespace).Str("name", key.name).Str("domain", config.Config.Domain).Msg("Domain is invalid, matching will rely on app name")
|
||||
} else {
|
||||
if slices.ContainsFunc(result.hosts, func(host string) bool {
|
||||
return hostMatchesHostname(host, config.Config.Domain)
|
||||
}) {
|
||||
entries = append(entries, resourceEntry{
|
||||
name: name,
|
||||
app: config,
|
||||
})
|
||||
continue
|
||||
}
|
||||
}
|
||||
if result.Meta != nil {
|
||||
k.removeResource(*result.Meta)
|
||||
}
|
||||
|
||||
if slices.ContainsFunc(result.hosts, func(host string) bool {
|
||||
return hostCoversName(host, name)
|
||||
}) {
|
||||
entries = append(entries, resourceEntry{
|
||||
name: name,
|
||||
app: config,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if len(entries) == 0 {
|
||||
k.removeResource(key)
|
||||
return
|
||||
}
|
||||
|
||||
k.addResourceEntries(key, result.hosts, entries)
|
||||
k.addResource(result)
|
||||
}
|
||||
|
||||
func (k *KubernetesService) resyncGVR(res watchedResource, ctx context.Context) error {
|
||||
@@ -328,7 +272,7 @@ func (k *KubernetesService) resyncGVR(res watchedResource, ctx context.Context)
|
||||
k.log.App.Warn().Err(err).Str("res", res.pretty()).Msg("Failed to decode resource, skipping")
|
||||
continue
|
||||
}
|
||||
k.updateFromItem(res, newTypedItem)
|
||||
k.watchedItemChange(res, newTypedItem, watch.Modified)
|
||||
}
|
||||
k.log.App.Debug().Str("res", res.pretty()).Int("count", len(list.Items)).Msg("Resync complete")
|
||||
return nil
|
||||
@@ -358,14 +302,8 @@ func (k *KubernetesService) runWatcher(res watchedResource, w watch.Interface, r
|
||||
continue
|
||||
}
|
||||
switch event.Type {
|
||||
case watch.Added, watch.Modified:
|
||||
k.updateFromItem(res, newTypedItem)
|
||||
case watch.Deleted:
|
||||
k.removeResource(resourceKey{
|
||||
typ: res.typ,
|
||||
namespace: item.GetNamespace(),
|
||||
name: item.GetName(),
|
||||
})
|
||||
case watch.Added, watch.Modified, watch.Deleted:
|
||||
k.watchedItemChange(res, newTypedItem, event.Type)
|
||||
}
|
||||
case <-resyncTicker.C:
|
||||
if err := k.resyncGVR(res, ctx); err != nil {
|
||||
@@ -412,13 +350,13 @@ func (k *KubernetesService) watchGVR(res watchedResource, ctx context.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
func (k *KubernetesService) Lookup(domain string, locator func(name string, app *model.App) bool) error {
|
||||
func (k *KubernetesService) Lookup(locator func(name string, app *model.App) bool) error {
|
||||
if !k.connected {
|
||||
k.log.App.Debug().Msg("Kubernetes label provider not started, skipping")
|
||||
return nil
|
||||
}
|
||||
|
||||
k.getEntry(domain, locator)
|
||||
k.getEntry(locator)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//go:build ignore
|
||||
|
||||
package service
|
||||
|
||||
import (
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
//go:generate controller-gen object paths=$GOFILE
|
||||
//go:generate controller-gen rbac:roleName=tinyauth crd paths=./... output:crd:dir=./crds output:stdout
|
||||
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
|
||||
// Application is a set of access control rules that can be applied to a
|
||||
// specific domain. It is an alternative to environment variable or config-based
|
||||
// access controls.
|
||||
type Application struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
|
||||
Spec ApplicationSpec `json:"spec,omitempty"`
|
||||
}
|
||||
|
||||
// +k8s:deepcopy-gen=true
|
||||
|
||||
// ApplicationSpec describes the application to which this rule applies to
|
||||
type ApplicationSpec struct {
|
||||
Config AppConfig `json:"config,omitempty"`
|
||||
Users AppUsers `json:"users,omitempty"`
|
||||
OAuth AppOAuth `json:"oauth,omitempty"`
|
||||
IP AppIP `json:"ip,omitempty"`
|
||||
Response AppResponse `json:"response,omitempty"`
|
||||
Path AppPath `json:"path,omitempty"`
|
||||
LDAP AppLDAP `json:"ldap,omitempty"`
|
||||
}
|
||||
|
||||
// +k8s:deepcopy-gen=true
|
||||
|
||||
// AppConfig specifies configuration for the application
|
||||
type AppConfig struct {
|
||||
// +required
|
||||
Domain string `json:"domain,omitempty"`
|
||||
}
|
||||
|
||||
// +k8s:deepcopy-gen=true
|
||||
|
||||
// AppUsers specifies user access control rules
|
||||
type AppUsers struct {
|
||||
Allow string `json:"allow,omitempty"`
|
||||
Block string `json:"block,omitempty"`
|
||||
}
|
||||
|
||||
// +k8s:deepcopy-gen=true
|
||||
|
||||
// AppOAuth specifies OAuth access control rules
|
||||
type AppOAuth struct {
|
||||
Whitelist string `json:"whitelist,omitempty"`
|
||||
Groups string `json:"groups,omitempty"`
|
||||
}
|
||||
|
||||
// +k8s:deepcopy-gen=true
|
||||
|
||||
// AppLDAP specifies LDAP access control rules
|
||||
type AppLDAP struct {
|
||||
Groups string `json:"groups,omitempty"`
|
||||
}
|
||||
|
||||
// +k8s:deepcopy-gen=true
|
||||
|
||||
// AppIP specifies IP access control rules
|
||||
type AppIP struct {
|
||||
Allow []string `json:"allow,omitempty"`
|
||||
Block []string `json:"block,omitempty"`
|
||||
Bypass []string `json:"bypass,omitempty"`
|
||||
}
|
||||
|
||||
// +k8s:deepcopy-gen=true
|
||||
|
||||
// AppResponse specifies response headers and basic auth credentials
|
||||
type AppResponse struct {
|
||||
Headers []string `json:"headers,omitempty"`
|
||||
BasicAuth AppBasicAuth `json:"basicAuth,omitempty"`
|
||||
}
|
||||
|
||||
// +k8s:deepcopy-gen=true
|
||||
|
||||
// AppBasicAuth specifies basic auth credentials
|
||||
type AppBasicAuth struct {
|
||||
Username string `json:"username,omitempty"`
|
||||
PasswordSecretRef *corev1.SecretKeySelector `json:"passwordSecretRef,omitempty"`
|
||||
}
|
||||
|
||||
// +k8s:deepcopy-gen=true
|
||||
|
||||
// AppPath specifies path-based access control rules
|
||||
type AppPath struct {
|
||||
Allow string `json:"allow,omitempty"`
|
||||
Block string `json:"block,omitempty"`
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
---
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.22.0
|
||||
name: applications.tinyauth.app
|
||||
spec:
|
||||
group: tinyauth.app
|
||||
names:
|
||||
kind: Application
|
||||
listKind: ApplicationList
|
||||
plural: applications
|
||||
singular: application
|
||||
scope: Namespaced
|
||||
versions:
|
||||
- name: v1alpha1
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
description: |-
|
||||
Application is a set of access control rules that can be applied to a
|
||||
specific domain. It is an alternative to environment variable or config-based
|
||||
access controls.
|
||||
properties:
|
||||
apiVersion:
|
||||
description: |-
|
||||
APIVersion defines the versioned schema of this representation of an object.
|
||||
Servers should convert recognized schemas to the latest internal value, and
|
||||
may reject unrecognized values.
|
||||
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
|
||||
type: string
|
||||
kind:
|
||||
description: |-
|
||||
Kind is a string value representing the REST resource this object represents.
|
||||
Servers may infer this from the endpoint the client submits requests to.
|
||||
Cannot be updated.
|
||||
In CamelCase.
|
||||
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
spec:
|
||||
description: ApplicationSpec describes the application to which this rule
|
||||
applies to
|
||||
properties:
|
||||
config:
|
||||
description: AppConfig specifies configuration for the application
|
||||
properties:
|
||||
domain:
|
||||
type: string
|
||||
required:
|
||||
- domain
|
||||
type: object
|
||||
ip:
|
||||
description: AppIP specifies IP access control rules
|
||||
properties:
|
||||
allow:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
block:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
bypass:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
type: object
|
||||
ldap:
|
||||
description: AppLDAP specifies LDAP access control rules
|
||||
properties:
|
||||
groups:
|
||||
type: string
|
||||
type: object
|
||||
oauth:
|
||||
description: AppOAuth specifies OAuth access control rules
|
||||
properties:
|
||||
groups:
|
||||
type: string
|
||||
whitelist:
|
||||
type: string
|
||||
type: object
|
||||
path:
|
||||
description: AppPath specifies path-based access control rules
|
||||
properties:
|
||||
allow:
|
||||
type: string
|
||||
block:
|
||||
type: string
|
||||
type: object
|
||||
response:
|
||||
description: AppResponse specifies response headers and basic auth
|
||||
credentials
|
||||
properties:
|
||||
basicAuth:
|
||||
description: AppBasicAuth specifies basic auth credentials
|
||||
properties:
|
||||
passwordSecretRef:
|
||||
description: SecretKeySelector selects a key of a Secret.
|
||||
properties:
|
||||
key:
|
||||
description: The key of the secret to select from. Must
|
||||
be a valid secret key.
|
||||
type: string
|
||||
name:
|
||||
default: ""
|
||||
description: |-
|
||||
Name of the referent.
|
||||
This field is effectively required, but due to backwards compatibility is
|
||||
allowed to be empty. Instances of this type with an empty value here are
|
||||
almost certainly wrong.
|
||||
More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
|
||||
type: string
|
||||
optional:
|
||||
description: Specify whether the Secret or its key must
|
||||
be defined
|
||||
type: boolean
|
||||
required:
|
||||
- key
|
||||
type: object
|
||||
x-kubernetes-map-type: atomic
|
||||
username:
|
||||
type: string
|
||||
type: object
|
||||
headers:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
type: object
|
||||
users:
|
||||
description: AppUsers specifies user access control rules
|
||||
properties:
|
||||
allow:
|
||||
type: string
|
||||
block:
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
type: object
|
||||
served: true
|
||||
storage: true
|
||||
@@ -0,0 +1,3 @@
|
||||
package v1alpha1
|
||||
|
||||
//+groupName=tinyauth.app
|
||||
@@ -0,0 +1,40 @@
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
"github.com/tinyauthapp/tinyauth/internal/model"
|
||||
)
|
||||
|
||||
// ToInternalApp converts the ApplicationSpec to the internal App structure
|
||||
func (s *ApplicationSpec) ToInternalApp() model.App {
|
||||
return model.App{
|
||||
Config: model.AppConfig{
|
||||
Domain: s.Config.Domain,
|
||||
},
|
||||
Users: model.AppUsers{
|
||||
Allow: s.Users.Allow,
|
||||
Block: s.Users.Block,
|
||||
},
|
||||
OAuth: model.AppOAuth{
|
||||
Whitelist: s.OAuth.Whitelist,
|
||||
Groups: s.OAuth.Groups,
|
||||
},
|
||||
IP: model.AppIP{
|
||||
Allow: s.IP.Allow,
|
||||
Block: s.IP.Block,
|
||||
Bypass: s.IP.Bypass,
|
||||
},
|
||||
Response: model.AppResponse{
|
||||
Headers: s.Response.Headers,
|
||||
BasicAuth: model.AppBasicAuth{
|
||||
Username: s.Response.BasicAuth.Username,
|
||||
},
|
||||
},
|
||||
Path: model.AppPath{
|
||||
Allow: s.Path.Allow,
|
||||
Block: s.Path.Block,
|
||||
},
|
||||
LDAP: model.AppLDAP{
|
||||
Groups: s.LDAP.Groups,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
const GroupName = "tinyauth.app"
|
||||
const GroupVersion = "v1alpha1"
|
||||
|
||||
var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: GroupVersion}
|
||||
|
||||
var (
|
||||
SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes)
|
||||
AddToScheme = SchemeBuilder.AddToScheme
|
||||
)
|
||||
|
||||
func addKnownTypes(scheme *runtime.Scheme) error {
|
||||
scheme.AddKnownTypes(SchemeGroupVersion,
|
||||
&Application{},
|
||||
)
|
||||
|
||||
metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
//go:build !ignore_autogenerated
|
||||
|
||||
// Code generated by controller-gen. DO NOT EDIT.
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
"k8s.io/api/core/v1"
|
||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *AppBasicAuth) DeepCopyInto(out *AppBasicAuth) {
|
||||
*out = *in
|
||||
if in.PasswordSecretRef != nil {
|
||||
in, out := &in.PasswordSecretRef, &out.PasswordSecretRef
|
||||
*out = new(v1.SecretKeySelector)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AppBasicAuth.
|
||||
func (in *AppBasicAuth) DeepCopy() *AppBasicAuth {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(AppBasicAuth)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *AppConfig) DeepCopyInto(out *AppConfig) {
|
||||
*out = *in
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AppConfig.
|
||||
func (in *AppConfig) DeepCopy() *AppConfig {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(AppConfig)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *AppIP) DeepCopyInto(out *AppIP) {
|
||||
*out = *in
|
||||
if in.Allow != nil {
|
||||
in, out := &in.Allow, &out.Allow
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.Block != nil {
|
||||
in, out := &in.Block, &out.Block
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.Bypass != nil {
|
||||
in, out := &in.Bypass, &out.Bypass
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AppIP.
|
||||
func (in *AppIP) DeepCopy() *AppIP {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(AppIP)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *AppLDAP) DeepCopyInto(out *AppLDAP) {
|
||||
*out = *in
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AppLDAP.
|
||||
func (in *AppLDAP) DeepCopy() *AppLDAP {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(AppLDAP)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *AppOAuth) DeepCopyInto(out *AppOAuth) {
|
||||
*out = *in
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AppOAuth.
|
||||
func (in *AppOAuth) DeepCopy() *AppOAuth {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(AppOAuth)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *AppPath) DeepCopyInto(out *AppPath) {
|
||||
*out = *in
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AppPath.
|
||||
func (in *AppPath) DeepCopy() *AppPath {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(AppPath)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *AppResponse) DeepCopyInto(out *AppResponse) {
|
||||
*out = *in
|
||||
if in.Headers != nil {
|
||||
in, out := &in.Headers, &out.Headers
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
in.BasicAuth.DeepCopyInto(&out.BasicAuth)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AppResponse.
|
||||
func (in *AppResponse) DeepCopy() *AppResponse {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(AppResponse)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *AppUsers) DeepCopyInto(out *AppUsers) {
|
||||
*out = *in
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AppUsers.
|
||||
func (in *AppUsers) DeepCopy() *AppUsers {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(AppUsers)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Application) DeepCopyInto(out *Application) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||
in.Spec.DeepCopyInto(&out.Spec)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Application.
|
||||
func (in *Application) DeepCopy() *Application {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(Application)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *Application) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ApplicationSpec) DeepCopyInto(out *ApplicationSpec) {
|
||||
*out = *in
|
||||
out.Config = in.Config
|
||||
out.Users = in.Users
|
||||
out.OAuth = in.OAuth
|
||||
in.IP.DeepCopyInto(&out.IP)
|
||||
in.Response.DeepCopyInto(&out.Response)
|
||||
out.Path = in.Path
|
||||
out.LDAP = in.LDAP
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApplicationSpec.
|
||||
func (in *ApplicationSpec) DeepCopy() *ApplicationSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ApplicationSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
Reference in New Issue
Block a user