mirror of
https://github.com/tinyauthapp/tinyauth.git
synced 2026-09-24 03:33:31 +08:00
135 lines
3.5 KiB
Go
135 lines
3.5 KiB
Go
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
|
|
}
|
|
|
|
type KubernetesIngressExtractorInput struct {
|
|
Log *logger.Logger
|
|
}
|
|
|
|
func NewKubernetesIngressExtractor(i KubernetesIngressExtractorInput) *KubernetesIngressExtractor {
|
|
return &KubernetesIngressExtractor{
|
|
log: i.Log,
|
|
}
|
|
}
|
|
|
|
func (k *KubernetesIngressExtractor) getPaths(rule networking.IngressRule) []string {
|
|
var paths []string
|
|
|
|
if rule.HTTP == nil {
|
|
return paths
|
|
}
|
|
|
|
for _, path := range rule.HTTP.Paths {
|
|
paths = append(paths, path.Path)
|
|
}
|
|
|
|
return paths
|
|
}
|
|
|
|
func (k *KubernetesIngressExtractor) getHosts(rules []networking.IngressRule) []string {
|
|
var hosts []string
|
|
|
|
for _, rule := range rules {
|
|
hosts = append(hosts, rule.Host)
|
|
paths := k.getPaths(rule)
|
|
|
|
if len(paths) == 0 {
|
|
continue
|
|
}
|
|
|
|
if !slices.Contains(paths, "/") {
|
|
k.log.App.Warn().Strs("hosts", hosts).Strs("paths", paths).Msg("Ingress rule does not contain a catch-all path, another ingress may be able to bypass auth checks if it routes the same host with a different path. Consider adding a catch-all path to this rule to ensure auth checks are applied to all paths for this host.")
|
|
}
|
|
}
|
|
|
|
return hosts
|
|
}
|
|
|
|
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)
|
|
|
|
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,
|
|
}
|
|
}
|