From 859add3c7f99c19d4652d5a86d7357159fee7641 Mon Sep 17 00:00:00 2001 From: Tomas Liumparas Date: Thu, 5 Dec 2024 03:26:47 +0200 Subject: [PATCH 1/9] Initial access config support --- api/v1alpha1/tunnelbinding_types.go | 141 +++++++++++++++++- api/v1alpha1/zz_generated.deepcopy.go | 138 +++++++++++++++++ ...rking.cfargotunnel.com_clustertunnels.yaml | 3 +- ...rking.cfargotunnel.com_tunnelbindings.yaml | 127 +++++++++++++++- .../networking.cfargotunnel.com_tunnels.yaml | 3 +- controllers/cloudflare_api.go | 43 ++++++ controllers/tunnelbinding_controller.go | 13 ++ go.mod | 9 +- go.sum | 14 +- 9 files changed, 468 insertions(+), 23 deletions(-) diff --git a/api/v1alpha1/tunnelbinding_types.go b/api/v1alpha1/tunnelbinding_types.go index cea33c5..62162bf 100644 --- a/api/v1alpha1/tunnelbinding_types.go +++ b/api/v1alpha1/tunnelbinding_types.go @@ -17,6 +17,10 @@ limitations under the License. package v1alpha1 import ( + "strconv" + + "github.com/cloudflare/cloudflare-go" + "github.com/howeyc/crc16" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -118,6 +122,139 @@ type TunnelBindingStatus struct { Services []ServiceInfo `json:"services"` } +type AccessConfig struct { + // Application name + //+kubebuilder:validation:Optional + Name string `json:"name"` + // Application domain + //+kubebuilder:validation:Optional + Domain string `json:"domain"` + // Application type self_hosted,saas + //+kubebuilder:validation:Optional + //+kubebuilder:validation:Enum:="";"self_hosted";"saas" + Type string `json:"type"` + // List of access policies + //+kubebuilder:validation:Optional + AccessPolicies []AccessPolicy `json:"accessPolicies"` + // Application settings + //+kubebuilder:validation:Optional + Settings AccessConfigSettings `json:"settings"` +} + +type AccessConfigSettings struct { + // Authentication settins + //+kubebuilder:validation:Optional + Authentication AccessConfigAuthentication `json:"authentication"` + // Appearance settins + //+kubebuilder:validation:Optional + Appearance AccessConfigAppearance `json:"appearance"` + // Cookie settings + //+kubebuilder:validation:Optional + Cookies AccessConfigCookies `json:"cookies"` + // Additional settings + //+kubebuilder:validation:Optional + Additional AccessConfigAdditional `json:"additional"` +} + +type AccessConfigAuthentication struct { + // The list of identiy providers which application is allowed to use. If empty all idps are allowed + //+kubebuilder:validation:Optional + AllowedIdps []string `json:"allowedIdps"` + // Skip identity provider selection if only one is configured + //+kubebuilder:validation:Optional + //+kubebuilder:default:=false + InstantAuth bool `json:"instantAuth"` + // The amount of time that tokens issued for this application will be valid. Must be in the format 300ms or 2h45m. Valid time units are: ns, us (or µs), ms, s, m, h. + //+kubebuilder:validation:Optional + //+kubebuilder:default:="24h" + SessionDuration string `json:"sessionDuration"` + // The custom URL a user is redirected to when they are denied access to the application. + //+kubebuilder:validation:Optional + CustomDenyUrl string `json:"customDenyUrl"` + // The custom error message shown to a user when they are denied access to the application. + //+kubebuilder:validation:Optional + CustomDenyMessage string `json:"customDenyMessage"` +} + +type AccessConfigAppearance struct { + // Wether to show app in the launcher. Defaults to true. + //+kubebuilder:validation:Optional + //+kubebuilder:default:=true + AppLauncherVisibility bool `json:"appLauncherVisibility"` + // Custom logo url + //+kubebuilder:validation:Optional + CustomLogo string `json:"customLogo"` +} + +type AccessConfigCookies struct { + // Sets the SameSite cookie setting, which provides increased security against CSRF attacks. [none,strict,lax] + //+kubebuilder:validation:Optional + //+kubebuilder:validation:Enum:="";"none";"strict";"lax" + SameSiteAttribute string `json:"sameSiteAttribute"` + // Enables the HttpOnly cookie attribute, which increases security against XSS attacks. + //+kubebuilder:validation:Optional + //+kubebuilder:default:=true + EnableHttpOnly bool `json:"enableHttpOnly"` + // Enables the binding cookie, which increases security against compromised authorization tokens and CSRF attacks. + //+kubebuilder:validation:Optional + //+kubebuilder:default:=false + EnableBindingCookie bool `json:"enableBindingCookie"` +} + +type AccessConfigAdditional struct { + // Cloudflare will render an SSH terminal or VNC session for this application in a web browser. [ssh,vnc] + //+kubebuilder:validation:Optional + //+kubebuilder:validation:Enum:="";"vnc";"ssh" + BrowserRendering string `json:"browserRendering"` +} + +type AccessPolicy struct { + Name string `json:"name"` + Action string `json:"action"` + Include []string `json:"include"` + Exclude []string `json:"exclude"` + Require []string `json:"require"` +} + +func (c *AccessConfig) NewAccessApplication() cloudflare.AccessApplication { + + uid := strconv.FormatInt(int64(crc16.Checksum([]byte(c.Name), crc16.CCITTTable)), 16) + return cloudflare.AccessApplication{ + // GatewayRules: []cloudflare.AccessApplicationGatewayRule{}, + AllowedIdps: c.Settings.Authentication.AllowedIdps, + CustomDenyMessage: c.Settings.Authentication.CustomDenyMessage, + LogoURL: c.Settings.Appearance.CustomLogo, + // AUD: "", + Domain: c.Domain, + Type: cloudflare.AccessApplicationType(c.Type), + SessionDuration: c.Settings.Authentication.SessionDuration, + SameSiteCookieAttribute: c.Settings.Cookies.SameSiteAttribute, + CustomDenyURL: c.Settings.Authentication.CustomDenyUrl, + Name: c.Name, + ID: uid, + // PrivateAddress: "", + // CorsHeaders: &cloudflare.AccessApplicationCorsHeaders{ + // AllowedMethods: []string{}, + // AllowedOrigins: []string{}, + // AllowedHeaders: []string{}, + // AllowAllMethods: false, + // AllowAllHeaders: false, + // AllowAllOrigins: false, + // AllowCredentials: false, + // MaxAge: 0, + // }, + // CreatedAt: &time.Time{}, + // UpdatedAt: &time.Time{}, + // SaasApplication: &cloudflare.SaasApplication{}, + AutoRedirectToIdentity: &c.Settings.Authentication.InstantAuth, + // SkipInterstitial: new(bool), + AppLauncherVisible: &c.Settings.Appearance.AppLauncherVisibility, + EnableBindingCookie: &c.Settings.Cookies.EnableBindingCookie, + HttpOnlyCookieAttribute: &c.Settings.Cookies.EnableHttpOnly, + // ServiceAuth401Redirect: new(bool), + } +} + //+kubebuilder:object:root=true //+kubebuilder:subresource:status //+kubebuilder:printcolumn:name="FQDNs",type=string,JSONPath=`.status.hostnames` @@ -129,7 +266,9 @@ type TunnelBinding struct { Subjects []TunnelBindingSubject `json:"subjects"` TunnelRef TunnelRef `json:"tunnelRef"` - Status TunnelBindingStatus `json:"status,omitempty"` + //+kubebuilder:validation:Optional + AccessConfig AccessConfig `json:"accessConfig"` + Status TunnelBindingStatus `json:"status,omitempty"` } //+kubebuilder:object:root=true diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 8b7031f..cee7a13 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -26,6 +26,143 @@ import ( 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 *AccessConfig) DeepCopyInto(out *AccessConfig) { + *out = *in + if in.AccessPolicies != nil { + in, out := &in.AccessPolicies, &out.AccessPolicies + *out = make([]AccessPolicy, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + in.Settings.DeepCopyInto(&out.Settings) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AccessConfig. +func (in *AccessConfig) DeepCopy() *AccessConfig { + if in == nil { + return nil + } + out := new(AccessConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AccessConfigAdditional) DeepCopyInto(out *AccessConfigAdditional) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AccessConfigAdditional. +func (in *AccessConfigAdditional) DeepCopy() *AccessConfigAdditional { + if in == nil { + return nil + } + out := new(AccessConfigAdditional) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AccessConfigAppearance) DeepCopyInto(out *AccessConfigAppearance) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AccessConfigAppearance. +func (in *AccessConfigAppearance) DeepCopy() *AccessConfigAppearance { + if in == nil { + return nil + } + out := new(AccessConfigAppearance) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AccessConfigAuthentication) DeepCopyInto(out *AccessConfigAuthentication) { + *out = *in + if in.AllowedIdps != nil { + in, out := &in.AllowedIdps, &out.AllowedIdps + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AccessConfigAuthentication. +func (in *AccessConfigAuthentication) DeepCopy() *AccessConfigAuthentication { + if in == nil { + return nil + } + out := new(AccessConfigAuthentication) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AccessConfigCookies) DeepCopyInto(out *AccessConfigCookies) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AccessConfigCookies. +func (in *AccessConfigCookies) DeepCopy() *AccessConfigCookies { + if in == nil { + return nil + } + out := new(AccessConfigCookies) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AccessConfigSettings) DeepCopyInto(out *AccessConfigSettings) { + *out = *in + in.Authentication.DeepCopyInto(&out.Authentication) + out.Appearance = in.Appearance + out.Cookies = in.Cookies + out.Additional = in.Additional +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AccessConfigSettings. +func (in *AccessConfigSettings) DeepCopy() *AccessConfigSettings { + if in == nil { + return nil + } + out := new(AccessConfigSettings) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AccessPolicy) DeepCopyInto(out *AccessPolicy) { + *out = *in + if in.Include != nil { + in, out := &in.Include, &out.Include + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.Exclude != nil { + in, out := &in.Exclude, &out.Exclude + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.Require != nil { + in, out := &in.Require, &out.Require + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AccessPolicy. +func (in *AccessPolicy) DeepCopy() *AccessPolicy { + if in == nil { + return nil + } + out := new(AccessPolicy) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *CloudflareDetails) DeepCopyInto(out *CloudflareDetails) { *out = *in @@ -183,6 +320,7 @@ func (in *TunnelBinding) DeepCopyInto(out *TunnelBinding) { copy(*out, *in) } out.TunnelRef = in.TunnelRef + in.AccessConfig.DeepCopyInto(&out.AccessConfig) in.Status.DeepCopyInto(&out.Status) } diff --git a/config/crd/bases/networking.cfargotunnel.com_clustertunnels.yaml b/config/crd/bases/networking.cfargotunnel.com_clustertunnels.yaml index 57e47f3..e7464c0 100644 --- a/config/crd/bases/networking.cfargotunnel.com_clustertunnels.yaml +++ b/config/crd/bases/networking.cfargotunnel.com_clustertunnels.yaml @@ -1,10 +1,9 @@ - --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.7.0 + controller-gen.kubebuilder.io/version: v0.8.0 creationTimestamp: null name: clustertunnels.networking.cfargotunnel.com spec: diff --git a/config/crd/bases/networking.cfargotunnel.com_tunnelbindings.yaml b/config/crd/bases/networking.cfargotunnel.com_tunnelbindings.yaml index 33aee05..d30875d 100644 --- a/config/crd/bases/networking.cfargotunnel.com_tunnelbindings.yaml +++ b/config/crd/bases/networking.cfargotunnel.com_tunnelbindings.yaml @@ -1,10 +1,9 @@ - --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.7.0 + controller-gen.kubebuilder.io/version: v0.8.0 creationTimestamp: null name: tunnelbindings.networking.cfargotunnel.com spec: @@ -25,6 +24,130 @@ spec: openAPIV3Schema: description: TunnelBinding is the Schema for the tunnelbindings API properties: + accessConfig: + properties: + accessPolicies: + description: List of access policies + items: + properties: + action: + type: string + exclude: + items: + type: string + type: array + include: + items: + type: string + type: array + name: + type: string + require: + items: + type: string + type: array + required: + - action + - exclude + - include + - name + - require + type: object + type: array + domain: + description: Application domain + type: string + name: + description: Application name + type: string + settings: + description: Application settings + properties: + additional: + description: Additional settings + properties: + browserRendering: + description: Cloudflare will render an SSH terminal or VNC + session for this application in a web browser. [ssh,vnc] + enum: + - "" + - vnc + - ssh + type: string + type: object + appearance: + description: Appearance settins + properties: + appLauncherVisibility: + default: true + description: Wether to show app in the launcher. Defaults + to true. + type: boolean + customLogo: + description: Custom logo url + type: string + type: object + authentication: + description: Authentication settins + properties: + allowedIdps: + description: The list of identiy providers which application + is allowed to use. If empty all idps are allowed + items: + type: string + type: array + customDenyMessage: + description: The custom error message shown to a user when + they are denied access to the application. + type: string + customDenyUrl: + description: The custom URL a user is redirected to when they + are denied access to the application. + type: string + instantAuth: + default: false + description: Skip identity provider selection if only one + is configured + type: boolean + sessionDuration: + default: 24h + description: 'The amount of time that tokens issued for this + application will be valid. Must be in the format 300ms or + 2h45m. Valid time units are: ns, us (or µs), ms, s, m, h.' + type: string + type: object + cookies: + description: Cookie settings + properties: + enableBindingCookie: + default: false + description: Enables the binding cookie, which increases security + against compromised authorization tokens and CSRF attacks. + type: boolean + enableHttpOnly: + default: true + description: Enables the HttpOnly cookie attribute, which + increases security against XSS attacks. + type: boolean + sameSiteAttribute: + description: Sets the SameSite cookie setting, which provides + increased security against CSRF attacks. [none,strict,lax] + enum: + - "" + - none + - strict + - lax + type: string + type: object + type: object + type: + description: Application type self_hosted,saas + enum: + - "" + - self_hosted + - saas + type: string + type: object apiVersion: description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest diff --git a/config/crd/bases/networking.cfargotunnel.com_tunnels.yaml b/config/crd/bases/networking.cfargotunnel.com_tunnels.yaml index d0d65c2..55c3135 100644 --- a/config/crd/bases/networking.cfargotunnel.com_tunnels.yaml +++ b/config/crd/bases/networking.cfargotunnel.com_tunnels.yaml @@ -1,10 +1,9 @@ - --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.7.0 + controller-gen.kubebuilder.io/version: v0.8.0 creationTimestamp: null name: tunnels.networking.cfargotunnel.com spec: diff --git a/controllers/cloudflare_api.go b/controllers/cloudflare_api.go index 6e7135d..cce8595 100644 --- a/controllers/cloudflare_api.go +++ b/controllers/cloudflare_api.go @@ -7,6 +7,7 @@ import ( "encoding/json" "fmt" + networkingv1alpha1 "github.com/adyanth/cloudflare-operator/api/v1alpha1" "github.com/cloudflare/cloudflare-go" "github.com/go-logr/logr" ) @@ -546,3 +547,45 @@ func (c *CloudflareAPI) InsertOrUpdateTXT(fqdn, txtId, dnsId string) error { return nil } } + +func (c *CloudflareAPI) UpdateAccessConfig(config networkingv1alpha1.AccessConfig) error { + ctx := context.Background() + newApp := config.NewAccessApplication() + accountId := c.ValidAccountId + + existingApps, _, err := c.CloudflareClient.AccessApplications(ctx, accountId, cloudflare.PaginationOptions{}) + if err != nil { + c.Log.Error(err, "failed to retrieve access applications for account", "accountId", accountId) + return err + } + + exists := false + for _, existingApp := range existingApps { + if existingApp.ID == newApp.ID { + exists = true + c.Log.Info("access application already exists, updating", "name", newApp.Name) + break + } + } + + if !exists { + c.Log.Info("creating access application", "name", newApp.Name, "id", newApp.ID) + + _, err := c.CloudflareClient.CreateAccessApplication(ctx, accountId, newApp) + newApp.ID = "" + + if err != nil { + c.Log.Error(err, "error creating access application", "name", newApp.Name) + return err + } + } else { + _, err := c.CloudflareClient.UpdateAccessApplication(ctx, accountId, newApp) + if err != nil { + c.Log.Error(err, "error updating access application", "name", newApp.Name) + return err + } + } + + c.Log.Info("access application created successfully", "name", config.Name) + return nil +} diff --git a/controllers/tunnelbinding_controller.go b/controllers/tunnelbinding_controller.go index f462ff4..419a255 100644 --- a/controllers/tunnelbinding_controller.go +++ b/controllers/tunnelbinding_controller.go @@ -295,6 +295,11 @@ func (r *TunnelBindingReconciler) creationLogic() error { r.Recorder.Event(r.binding, corev1.EventTypeWarning, "FailedDNSCreatePartial", "Some DNS entries failed to create") return err } + + // Create AccessApp + if r.binding.AccessConfig.Name != "" { + r.createAccessConfigLogic(r.binding.AccessConfig) + } return nil } @@ -388,6 +393,14 @@ func (r *TunnelBindingReconciler) deleteDNSLogic(hostname string) error { return nil } +func (r *TunnelBindingReconciler) createAccessConfigLogic(config networkingv1alpha1.AccessConfig) error { + err := r.cfAPI.UpdateAccessConfig(config) + if err != nil { + return err + } + return nil +} + func (r *TunnelBindingReconciler) getRelevantTunnelBindings() ([]networkingv1alpha1.TunnelBinding, error) { // Fetch TunnelBindings from API listOpts := []client.ListOption{client.MatchingLabels(map[string]string{ diff --git a/go.mod b/go.mod index f7244ad..2fdb900 100644 --- a/go.mod +++ b/go.mod @@ -5,8 +5,10 @@ go 1.19 require ( github.com/cloudflare/cloudflare-go v0.63.0 github.com/go-logr/logr v1.2.3 + github.com/howeyc/crc16 v0.0.0-20171223171357-2b2a61e366a6 github.com/onsi/ginkgo v1.16.5 github.com/onsi/gomega v1.19.0 + go.uber.org/zap v1.21.0 gopkg.in/yaml.v3 v3.0.1 k8s.io/api v0.25.0 k8s.io/apimachinery v0.25.0 @@ -15,7 +17,6 @@ require ( ) require ( - 9fans.net/go v0.0.0-20181112161441-237454027057 // indirect cloud.google.com/go v0.97.0 // indirect github.com/Azure/go-autorest v14.2.0+incompatible // indirect github.com/Azure/go-autorest/autorest v0.11.27 // indirect @@ -43,7 +44,7 @@ require ( github.com/google/go-cmp v0.5.8 // indirect github.com/google/go-querystring v1.1.0 // indirect github.com/google/gofuzz v1.1.0 // indirect - github.com/google/uuid v1.1.2 // indirect + github.com/google/uuid v1.6.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-retryablehttp v0.7.2 // indirect github.com/imdario/mergo v0.3.12 // indirect @@ -60,20 +61,16 @@ require ( github.com/prometheus/client_model v0.2.0 // indirect github.com/prometheus/common v0.32.1 // indirect github.com/prometheus/procfs v0.7.3 // indirect - github.com/rogpeppe/godef v1.1.2 // indirect github.com/spf13/pflag v1.0.5 // indirect go.uber.org/atomic v1.7.0 // indirect go.uber.org/multierr v1.6.0 // indirect - go.uber.org/zap v1.21.0 // indirect golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd // indirect - golang.org/x/mod v0.8.0 // indirect golang.org/x/net v0.8.0 // indirect golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect golang.org/x/sys v0.6.0 // indirect golang.org/x/term v0.6.0 // indirect golang.org/x/text v0.8.0 // indirect golang.org/x/time v0.3.0 // indirect - golang.org/x/tools v0.6.0 // indirect gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/protobuf v1.28.0 // indirect diff --git a/go.sum b/go.sum index 5df1cdd..b6e67c3 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,3 @@ -9fans.net/go v0.0.0-20181112161441-237454027057 h1:OcHlKWkAMJEF1ndWLGxp5dnJQkYM/YImUOvsBoz6h5E= -9fans.net/go v0.0.0-20181112161441-237454027057/go.mod h1:diCsxrliIURU9xsYtjCp5AbpQKqdhKmf0ujWDUSkfoY= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= @@ -229,8 +227,9 @@ github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= @@ -243,6 +242,8 @@ github.com/hashicorp/go-retryablehttp v0.7.2 h1:AcYqCvkpalPnPF2pn0KamgwamS42TqUD github.com/hashicorp/go-retryablehttp v0.7.2/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/howeyc/crc16 v0.0.0-20171223171357-2b2a61e366a6 h1:IIVxLyDUYErC950b8kecjoqDet8P5S4lcVRUOM6rdkU= +github.com/howeyc/crc16 v0.0.0-20171223171357-2b2a61e366a6/go.mod h1:JslaLRrzGsOKJgFEPBP65Whn+rdwDQSk0I0MCRFe2Zw= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= @@ -337,8 +338,6 @@ github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1 github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= -github.com/rogpeppe/godef v1.1.2 h1:c5mCx0EcCORJOdVMREX7Lgh1raTxAHFmOfXdEB9u8Jw= -github.com/rogpeppe/godef v1.1.2/go.mod h1:WtY9A/ovuQ+UakAJ1/CEqwwulX/WJjb2kgkokCHi/GY= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= @@ -424,8 +423,6 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8= -golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -609,7 +606,6 @@ golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapK golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200226224502-204d844ad48d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= @@ -635,8 +631,6 @@ golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= -golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= From 9348830fe5aacb95dd3f161d208a367907aa7f8b Mon Sep 17 00:00:00 2001 From: Tomas Liumparas Date: Thu, 5 Dec 2024 15:52:21 +0200 Subject: [PATCH 2/9] Add deletion logic --- controllers/cloudflare_api.go | 72 +++++++++++++++++++------ controllers/tunnelbinding_controller.go | 23 +++++++- 2 files changed, 76 insertions(+), 19 deletions(-) diff --git a/controllers/cloudflare_api.go b/controllers/cloudflare_api.go index cce8595..73c0902 100644 --- a/controllers/cloudflare_api.go +++ b/controllers/cloudflare_api.go @@ -548,44 +548,82 @@ func (c *CloudflareAPI) InsertOrUpdateTXT(fqdn, txtId, dnsId string) error { } } -func (c *CloudflareAPI) UpdateAccessConfig(config networkingv1alpha1.AccessConfig) error { +func (c *CloudflareAPI) getAccessApplicationIdByName(name string) (exists bool, id string, error error) { ctx := context.Background() - newApp := config.NewAccessApplication() accountId := c.ValidAccountId - existingApps, _, err := c.CloudflareClient.AccessApplications(ctx, accountId, cloudflare.PaginationOptions{}) + apps, _, err := c.CloudflareClient.AccessApplications(ctx, accountId, cloudflare.PaginationOptions{}) if err != nil { - c.Log.Error(err, "failed to retrieve access applications for account", "accountId", accountId) - return err + return false, "", err } - exists := false - for _, existingApp := range existingApps { - if existingApp.ID == newApp.ID { + exists = false + for _, app := range apps { + if app.Name == name { exists = true - c.Log.Info("access application already exists, updating", "name", newApp.Name) + id = app.ID break } } + return exists, id, nil +} - if !exists { - c.Log.Info("creating access application", "name", newApp.Name, "id", newApp.ID) +func (c *CloudflareAPI) CreateAccessConfig(config networkingv1alpha1.AccessConfig) error { + ctx := context.Background() + newApp := config.NewAccessApplication() + accountId := c.ValidAccountId + name := newApp.Name - _, err := c.CloudflareClient.CreateAccessApplication(ctx, accountId, newApp) - newApp.ID = "" + exists, id, err := c.getAccessApplicationIdByName(name) + if err != nil { + c.Log.Error(err, "failed retrieving application for account", "accountId", accountId, "app", name) + } + if exists { + newApp.ID = id + c.Log.Info("updating access application", "name", name, "id", id) + _, err := c.CloudflareClient.UpdateAccessApplication(ctx, accountId, newApp) if err != nil { - c.Log.Error(err, "error creating access application", "name", newApp.Name) + c.Log.Error(err, "error updating access application", "name", name) return err } } else { - _, err := c.CloudflareClient.UpdateAccessApplication(ctx, accountId, newApp) + c.Log.Info("creating access application", "name", name) + newApp.ID = "" + _, err := c.CloudflareClient.CreateAccessApplication(ctx, accountId, newApp) if err != nil { - c.Log.Error(err, "error updating access application", "name", newApp.Name) + c.Log.Error(err, "error creating access application", "name", name) return err } } - c.Log.Info("access application created successfully", "name", config.Name) + c.Log.Info("access application reconciled successfully", "name", name, "existing", exists) + return nil +} + +func (c *CloudflareAPI) DeleteAccessConfig(config networkingv1alpha1.AccessConfig) error { + ctx := context.Background() + newApp := config.NewAccessApplication() + accountId := c.ValidAccountId + name := newApp.Name + + exists, id, err := c.getAccessApplicationIdByName(name) + if err != nil { + c.Log.Error(err, "failed retrieving application for account", "accountId", accountId, "app", name) + } + + if exists { + c.Log.Info("deleting access application", "name", name, "id", id) + err := c.CloudflareClient.DeleteAccessApplication(ctx, accountId, id) + if err != nil { + c.Log.Error(err, "error deleting access application", "name", name) + return err + } + } else { + err := fmt.Errorf("application does not exist", "name", newApp.Name, "id", newApp.ID) + return err + } + + c.Log.Info("access application deleted successfully", "name", config.Name, "existing", exists) return nil } diff --git a/controllers/tunnelbinding_controller.go b/controllers/tunnelbinding_controller.go index 419a255..d4124d2 100644 --- a/controllers/tunnelbinding_controller.go +++ b/controllers/tunnelbinding_controller.go @@ -232,6 +232,14 @@ func (r *TunnelBindingReconciler) deletionLogic() error { return err } + // Remove AccessApp + if r.binding.AccessConfig.Name != "" { + err := r.deleteAccessConfigLogic(r.binding.AccessConfig) + if err != nil { + return err + } + } + // Remove tunnelFinalizer. Once all finalizers have been // removed, the object will be deleted. controllerutil.RemoveFinalizer(r.binding, tunnelFinalizer) @@ -298,7 +306,10 @@ func (r *TunnelBindingReconciler) creationLogic() error { // Create AccessApp if r.binding.AccessConfig.Name != "" { - r.createAccessConfigLogic(r.binding.AccessConfig) + err := r.createAccessConfigLogic(r.binding.AccessConfig) + if err != nil { + return err + } } return nil } @@ -394,7 +405,15 @@ func (r *TunnelBindingReconciler) deleteDNSLogic(hostname string) error { } func (r *TunnelBindingReconciler) createAccessConfigLogic(config networkingv1alpha1.AccessConfig) error { - err := r.cfAPI.UpdateAccessConfig(config) + err := r.cfAPI.CreateAccessConfig(config) + if err != nil { + return err + } + return nil +} + +func (r *TunnelBindingReconciler) deleteAccessConfigLogic(config networkingv1alpha1.AccessConfig) error { + err := r.cfAPI.DeleteAccessConfig(config) if err != nil { return err } From 1935b1221731bfc37a82e98bb733ec07942cda78 Mon Sep 17 00:00:00 2001 From: Tomas Liumparas Date: Thu, 5 Dec 2024 16:17:26 +0200 Subject: [PATCH 3/9] Reconcile AccessConfig before DNS, add events --- controllers/tunnelbinding_controller.go | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/controllers/tunnelbinding_controller.go b/controllers/tunnelbinding_controller.go index d4124d2..f5ac6ad 100644 --- a/controllers/tunnelbinding_controller.go +++ b/controllers/tunnelbinding_controller.go @@ -290,6 +290,14 @@ func (r *TunnelBindingReconciler) creationLogic() error { r.Recorder.Event(r.binding, corev1.EventTypeNormal, "MetaSet", "TunnelBinding Finalizer and Labels added") + // Create AccessApp + if r.binding.AccessConfig.Name != "" { + err := r.createAccessConfigLogic(r.binding.AccessConfig) + if err != nil { + return err + } + } + errors := false var err error // Create DNS entries @@ -304,13 +312,6 @@ func (r *TunnelBindingReconciler) creationLogic() error { return err } - // Create AccessApp - if r.binding.AccessConfig.Name != "" { - err := r.createAccessConfigLogic(r.binding.AccessConfig) - if err != nil { - return err - } - } return nil } @@ -407,16 +408,20 @@ func (r *TunnelBindingReconciler) deleteDNSLogic(hostname string) error { func (r *TunnelBindingReconciler) createAccessConfigLogic(config networkingv1alpha1.AccessConfig) error { err := r.cfAPI.CreateAccessConfig(config) if err != nil { + r.Recorder.Event(r.binding, corev1.EventTypeWarning, "FailedAccessConfig", "Failed to apply Access Configuration") return err } + r.Recorder.Event(r.binding, corev1.EventTypeNormal, "CreateAccessConfig", "Access Configuration applied successfully") return nil } func (r *TunnelBindingReconciler) deleteAccessConfigLogic(config networkingv1alpha1.AccessConfig) error { err := r.cfAPI.DeleteAccessConfig(config) if err != nil { + r.Recorder.Event(r.binding, corev1.EventTypeWarning, "FailedAccessConfig", "Failed to delete Access Configuration") return err } + r.Recorder.Event(r.binding, corev1.EventTypeNormal, "DeleteAccessConfig", "Access Configuration delleted successfully") return nil } From 621aec7d7ea31675b9ff862a3313bb5e9eae82e6 Mon Sep 17 00:00:00 2001 From: Tomas Liumparas Date: Thu, 5 Dec 2024 18:18:08 +0200 Subject: [PATCH 4/9] Use service fqdn --- api/v1alpha1/tunnelbinding_types.go | 17 +++--- ...rking.cfargotunnel.com_tunnelbindings.yaml | 11 ++-- controllers/cloudflare_api.go | 13 ++--- controllers/tunnelbinding_controller.go | 53 +++++++++++-------- 4 files changed, 49 insertions(+), 45 deletions(-) diff --git a/api/v1alpha1/tunnelbinding_types.go b/api/v1alpha1/tunnelbinding_types.go index 62162bf..31fc170 100644 --- a/api/v1alpha1/tunnelbinding_types.go +++ b/api/v1alpha1/tunnelbinding_types.go @@ -123,15 +123,14 @@ type TunnelBindingStatus struct { } type AccessConfig struct { - // Application name + // Enable handling of access configuration //+kubebuilder:validation:Optional - Name string `json:"name"` - // Application domain - //+kubebuilder:validation:Optional - Domain string `json:"domain"` + //+kubebuilder:default:=false + Enabled bool `json:"enabled"` // Application type self_hosted,saas //+kubebuilder:validation:Optional //+kubebuilder:validation:Enum:="";"self_hosted";"saas" + //+kubebuilder:default:="self_hosted" Type string `json:"type"` // List of access policies //+kubebuilder:validation:Optional @@ -216,21 +215,21 @@ type AccessPolicy struct { Require []string `json:"require"` } -func (c *AccessConfig) NewAccessApplication() cloudflare.AccessApplication { +func (c *AccessConfig) NewAccessApplication(hostname string) cloudflare.AccessApplication { - uid := strconv.FormatInt(int64(crc16.Checksum([]byte(c.Name), crc16.CCITTTable)), 16) + uid := strconv.FormatInt(int64(crc16.Checksum([]byte(hostname), crc16.CCITTTable)), 16) return cloudflare.AccessApplication{ // GatewayRules: []cloudflare.AccessApplicationGatewayRule{}, AllowedIdps: c.Settings.Authentication.AllowedIdps, CustomDenyMessage: c.Settings.Authentication.CustomDenyMessage, LogoURL: c.Settings.Appearance.CustomLogo, // AUD: "", - Domain: c.Domain, + Domain: hostname, Type: cloudflare.AccessApplicationType(c.Type), SessionDuration: c.Settings.Authentication.SessionDuration, SameSiteCookieAttribute: c.Settings.Cookies.SameSiteAttribute, CustomDenyURL: c.Settings.Authentication.CustomDenyUrl, - Name: c.Name, + Name: hostname, ID: uid, // PrivateAddress: "", // CorsHeaders: &cloudflare.AccessApplicationCorsHeaders{ diff --git a/config/crd/bases/networking.cfargotunnel.com_tunnelbindings.yaml b/config/crd/bases/networking.cfargotunnel.com_tunnelbindings.yaml index d30875d..1280dc6 100644 --- a/config/crd/bases/networking.cfargotunnel.com_tunnelbindings.yaml +++ b/config/crd/bases/networking.cfargotunnel.com_tunnelbindings.yaml @@ -54,12 +54,10 @@ spec: - require type: object type: array - domain: - description: Application domain - type: string - name: - description: Application name - type: string + enabled: + default: false + description: Enable handling of access configuration + type: boolean settings: description: Application settings properties: @@ -141,6 +139,7 @@ spec: type: object type: object type: + default: self_hosted description: Application type self_hosted,saas enum: - "" diff --git a/controllers/cloudflare_api.go b/controllers/cloudflare_api.go index 73c0902..ca86d4a 100644 --- a/controllers/cloudflare_api.go +++ b/controllers/cloudflare_api.go @@ -568,11 +568,10 @@ func (c *CloudflareAPI) getAccessApplicationIdByName(name string) (exists bool, return exists, id, nil } -func (c *CloudflareAPI) CreateAccessConfig(config networkingv1alpha1.AccessConfig) error { +func (c *CloudflareAPI) CreateAccessConfig(name string, config networkingv1alpha1.AccessConfig) error { ctx := context.Background() - newApp := config.NewAccessApplication() + newApp := config.NewAccessApplication(name) accountId := c.ValidAccountId - name := newApp.Name exists, id, err := c.getAccessApplicationIdByName(name) if err != nil { @@ -601,11 +600,9 @@ func (c *CloudflareAPI) CreateAccessConfig(config networkingv1alpha1.AccessConfi return nil } -func (c *CloudflareAPI) DeleteAccessConfig(config networkingv1alpha1.AccessConfig) error { +func (c *CloudflareAPI) DeleteAccessConfig(name string, config networkingv1alpha1.AccessConfig) error { ctx := context.Background() - newApp := config.NewAccessApplication() accountId := c.ValidAccountId - name := newApp.Name exists, id, err := c.getAccessApplicationIdByName(name) if err != nil { @@ -620,10 +617,10 @@ func (c *CloudflareAPI) DeleteAccessConfig(config networkingv1alpha1.AccessConfi return err } } else { - err := fmt.Errorf("application does not exist", "name", newApp.Name, "id", newApp.ID) + err := fmt.Errorf("application does not exist", "name", name, "id", id) return err } - c.Log.Info("access application deleted successfully", "name", config.Name, "existing", exists) + c.Log.Info("access application deleted successfully", "name", name, "existing", exists) return nil } diff --git a/controllers/tunnelbinding_controller.go b/controllers/tunnelbinding_controller.go index f5ac6ad..c0a5f84 100644 --- a/controllers/tunnelbinding_controller.go +++ b/controllers/tunnelbinding_controller.go @@ -56,6 +56,7 @@ type TunnelBindingReconciler struct { configmap *corev1.ConfigMap fallbackTarget string cfAPI *CloudflareAPI + domain string } // labelsForBinding returns the labels for selecting the Bindings served by a Tunnel. @@ -222,24 +223,29 @@ func (r *TunnelBindingReconciler) deletionLogic() error { errors := false var err error + + // Iterate over healthy services for _, info := range r.binding.Status.Services { + + // Delete DNS records if err = r.deleteDNSLogic(info.Hostname); err != nil { errors = true } + + // Remove AccessApp + if r.binding.AccessConfig.Enabled { + err := r.deleteAccessConfigLogic(info.Hostname, r.binding.AccessConfig) + if err != nil { + return err + } + } + } if errors { r.Recorder.Event(r.binding, corev1.EventTypeWarning, "FinalizerNotUnset", "Not removing Finalizer due to errors") return err } - // Remove AccessApp - if r.binding.AccessConfig.Name != "" { - err := r.deleteAccessConfigLogic(r.binding.AccessConfig) - if err != nil { - return err - } - } - // Remove tunnelFinalizer. Once all finalizers have been // removed, the object will be deleted. controllerutil.RemoveFinalizer(r.binding, tunnelFinalizer) @@ -290,18 +296,21 @@ func (r *TunnelBindingReconciler) creationLogic() error { r.Recorder.Event(r.binding, corev1.EventTypeNormal, "MetaSet", "TunnelBinding Finalizer and Labels added") - // Create AccessApp - if r.binding.AccessConfig.Name != "" { - err := r.createAccessConfigLogic(r.binding.AccessConfig) - if err != nil { - return err - } - } - errors := false var err error - // Create DNS entries + + // Iterate over healthy services for _, info := range r.binding.Status.Services { + + // Create AccessApp + if r.binding.AccessConfig.Enabled { + err := r.createAccessConfigLogic(info.Hostname, r.binding.AccessConfig) + if err != nil { + return err + } + } + + // Create DNS entries err = r.createDNSLogic(info.Hostname) if err != nil { errors = true @@ -405,8 +414,8 @@ func (r *TunnelBindingReconciler) deleteDNSLogic(hostname string) error { return nil } -func (r *TunnelBindingReconciler) createAccessConfigLogic(config networkingv1alpha1.AccessConfig) error { - err := r.cfAPI.CreateAccessConfig(config) +func (r *TunnelBindingReconciler) createAccessConfigLogic(name string, config networkingv1alpha1.AccessConfig) error { + err := r.cfAPI.CreateAccessConfig(name, config) if err != nil { r.Recorder.Event(r.binding, corev1.EventTypeWarning, "FailedAccessConfig", "Failed to apply Access Configuration") return err @@ -415,13 +424,13 @@ func (r *TunnelBindingReconciler) createAccessConfigLogic(config networkingv1alp return nil } -func (r *TunnelBindingReconciler) deleteAccessConfigLogic(config networkingv1alpha1.AccessConfig) error { - err := r.cfAPI.DeleteAccessConfig(config) +func (r *TunnelBindingReconciler) deleteAccessConfigLogic(name string, config networkingv1alpha1.AccessConfig) error { + err := r.cfAPI.DeleteAccessConfig(name, config) if err != nil { r.Recorder.Event(r.binding, corev1.EventTypeWarning, "FailedAccessConfig", "Failed to delete Access Configuration") return err } - r.Recorder.Event(r.binding, corev1.EventTypeNormal, "DeleteAccessConfig", "Access Configuration delleted successfully") + r.Recorder.Event(r.binding, corev1.EventTypeNormal, "DeleteAccessConfig", "Access Configuration deleted successfully") return nil } From 5f79d6d472d198db1ad1e3411230565a54355259 Mon Sep 17 00:00:00 2001 From: Tomas Liumparas Date: Thu, 5 Dec 2024 18:25:19 +0200 Subject: [PATCH 5/9] Remove uuid logic --- api/v1alpha1/tunnelbinding_types.go | 5 ----- go.mod | 1 - go.sum | 2 -- 3 files changed, 8 deletions(-) diff --git a/api/v1alpha1/tunnelbinding_types.go b/api/v1alpha1/tunnelbinding_types.go index 31fc170..3e19d4b 100644 --- a/api/v1alpha1/tunnelbinding_types.go +++ b/api/v1alpha1/tunnelbinding_types.go @@ -17,10 +17,7 @@ limitations under the License. package v1alpha1 import ( - "strconv" - "github.com/cloudflare/cloudflare-go" - "github.com/howeyc/crc16" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -217,7 +214,6 @@ type AccessPolicy struct { func (c *AccessConfig) NewAccessApplication(hostname string) cloudflare.AccessApplication { - uid := strconv.FormatInt(int64(crc16.Checksum([]byte(hostname), crc16.CCITTTable)), 16) return cloudflare.AccessApplication{ // GatewayRules: []cloudflare.AccessApplicationGatewayRule{}, AllowedIdps: c.Settings.Authentication.AllowedIdps, @@ -230,7 +226,6 @@ func (c *AccessConfig) NewAccessApplication(hostname string) cloudflare.AccessAp SameSiteCookieAttribute: c.Settings.Cookies.SameSiteAttribute, CustomDenyURL: c.Settings.Authentication.CustomDenyUrl, Name: hostname, - ID: uid, // PrivateAddress: "", // CorsHeaders: &cloudflare.AccessApplicationCorsHeaders{ // AllowedMethods: []string{}, diff --git a/go.mod b/go.mod index 2fdb900..ce2f3ea 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,6 @@ go 1.19 require ( github.com/cloudflare/cloudflare-go v0.63.0 github.com/go-logr/logr v1.2.3 - github.com/howeyc/crc16 v0.0.0-20171223171357-2b2a61e366a6 github.com/onsi/ginkgo v1.16.5 github.com/onsi/gomega v1.19.0 go.uber.org/zap v1.21.0 diff --git a/go.sum b/go.sum index b6e67c3..03f4611 100644 --- a/go.sum +++ b/go.sum @@ -242,8 +242,6 @@ github.com/hashicorp/go-retryablehttp v0.7.2 h1:AcYqCvkpalPnPF2pn0KamgwamS42TqUD github.com/hashicorp/go-retryablehttp v0.7.2/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/howeyc/crc16 v0.0.0-20171223171357-2b2a61e366a6 h1:IIVxLyDUYErC950b8kecjoqDet8P5S4lcVRUOM6rdkU= -github.com/howeyc/crc16 v0.0.0-20171223171357-2b2a61e366a6/go.mod h1:JslaLRrzGsOKJgFEPBP65Whn+rdwDQSk0I0MCRFe2Zw= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= From 03670052908c069700013709ac94d8d03b8daa38 Mon Sep 17 00:00:00 2001 From: Tomas Liumparas Date: Fri, 6 Dec 2024 01:29:57 +0200 Subject: [PATCH 6/9] Add access policy handling --- api/v1alpha1/tunnelbinding_types.go | 27 +++- ...rking.cfargotunnel.com_tunnelbindings.yaml | 33 +++- controllers/cloudflare_api.go | 146 +++++++++++++++++- 3 files changed, 200 insertions(+), 6 deletions(-) diff --git a/api/v1alpha1/tunnelbinding_types.go b/api/v1alpha1/tunnelbinding_types.go index 3e19d4b..d3b34e5 100644 --- a/api/v1alpha1/tunnelbinding_types.go +++ b/api/v1alpha1/tunnelbinding_types.go @@ -205,11 +205,34 @@ type AccessConfigAdditional struct { } type AccessPolicy struct { - Name string `json:"name"` - Action string `json:"action"` + // The name of the Access policy. + //+kubebuilder:validation:Required + Name string `json:"name"` + // Decision if a policy is met + //+kubebuilder:validation:Required + //+kubebuilder:validation:Enum:="allow";"deny";"non_identity";"bypass" + Action string `json:"action"` + // Array of Access group names. Access groups are not managed by this operator + //+kubebuilder:validation:Optional Include []string `json:"include"` + // Array of Access group names. Access groups are not managed by this operator + //+kubebuilder:validation:Optional Exclude []string `json:"exclude"` + // Array of Access group names. Access groups are not managed by this operator + //+kubebuilder:validation:Optional Require []string `json:"require"` + // The amount of time that tokens issued for the application will be valid. Must be in the format 300ms or 2h45m. Valid time units are: ns, us (or µs), ms, s, m, h. + //+kubebuilder:validation:Optional + //+kubebuilder:default:="24h" + SessionDuration string `json:"sessionDuration"` + // Require users to enter a justification when they log in to the application. + //+kubebuilder:validation:Optional + //+kubebuilder:default:=false + PurposeJustificationRequired bool `json:"purposeJustificationRequired"` + // A custom message that will appear on the purpose justification screen. + //+kubebuilder:validation:Optional + //+kubebuilder:default:="Please enter a justification for entering this protected domain." + PurposeJustificationPrompt string `json:"purposeJustificationPrompt"` } func (c *AccessConfig) NewAccessApplication(hostname string) cloudflare.AccessApplication { diff --git a/config/crd/bases/networking.cfargotunnel.com_tunnelbindings.yaml b/config/crd/bases/networking.cfargotunnel.com_tunnelbindings.yaml index 1280dc6..9f9755d 100644 --- a/config/crd/bases/networking.cfargotunnel.com_tunnelbindings.yaml +++ b/config/crd/bases/networking.cfargotunnel.com_tunnelbindings.yaml @@ -31,27 +31,54 @@ spec: items: properties: action: + description: Decision if a policy is met + enum: + - allow + - deny + - non_identity + - bypass type: string exclude: + description: Array of Access group names. Access groups are + not managed by this operator items: type: string type: array include: + description: Array of Access group names. Access groups are + not managed by this operator items: type: string type: array name: + description: The name of the Access policy. + type: string + purposeJustificationPrompt: + default: Please enter a justification for entering this protected + domain. + description: A custom message that will appear on the purpose + justification screen. type: string + purposeJustificationRequired: + default: false + description: Require users to enter a justification when they + log in to the application. + type: boolean require: + description: Array of Access group names. Access groups are + not managed by this operator items: type: string type: array + sessionDuration: + default: 24h + description: 'The amount of time that tokens issued for the + application will be valid. Must be in the format 300ms or + 2h45m. Valid time units are: ns, us (or µs), ms, s, m, h.' + type: string required: - action - - exclude - - include - name - - require type: object type: array enabled: diff --git a/controllers/cloudflare_api.go b/controllers/cloudflare_api.go index ca86d4a..b292f5d 100644 --- a/controllers/cloudflare_api.go +++ b/controllers/cloudflare_api.go @@ -568,6 +568,49 @@ func (c *CloudflareAPI) getAccessApplicationIdByName(name string) (exists bool, return exists, id, nil } +func (c *CloudflareAPI) getAccessPolicyIdByName(applicationId string, name string) (exists bool, id string, error error) { + ctx := context.Background() + accountId := c.ValidAccountId + + policies, _, err := c.CloudflareClient.AccessPolicies(ctx, accountId, applicationId, cloudflare.PaginationOptions{}) + if err != nil { + return false, "", err + } + + exists = false + for _, policy := range policies { + if policy.Name == name { + exists = true + id = policy.ID + break + } + } + return exists, id, nil +} + +func (c *CloudflareAPI) getAccessGroupIdsByNames(names []string) (ids []string, error error) { + ctx := context.Background() + accountId := c.ValidAccountId + + groups, _, err := c.CloudflareClient.AccessGroups(ctx, accountId, cloudflare.PaginationOptions{}) + if err != nil { + return ids, err + } + +outerLoop: + // Match Access group names to ids + for _, group := range groups { + for _, name := range names { + if group.Name == name { + ids = append(ids, group.ID) + continue outerLoop // Break inner loop and continue the outer loop + } + } + } + + return ids, nil +} + func (c *CloudflareAPI) CreateAccessConfig(name string, config networkingv1alpha1.AccessConfig) error { ctx := context.Background() newApp := config.NewAccessApplication(name) @@ -589,11 +632,20 @@ func (c *CloudflareAPI) CreateAccessConfig(name string, config networkingv1alpha } else { c.Log.Info("creating access application", "name", name) newApp.ID = "" - _, err := c.CloudflareClient.CreateAccessApplication(ctx, accountId, newApp) + createdApp, err := c.CloudflareClient.CreateAccessApplication(ctx, accountId, newApp) if err != nil { c.Log.Error(err, "error creating access application", "name", name) return err } + + // Set id, so that it can be used in policy creation + id = createdApp.ID + } + + err = c.createAccessApplicationPolicies(accountId, id, config) + if err != nil { + c.Log.Error(err, "error creating access application policies", "name", name) + return err } c.Log.Info("access application reconciled successfully", "name", name, "existing", exists) @@ -624,3 +676,95 @@ func (c *CloudflareAPI) DeleteAccessConfig(name string, config networkingv1alpha c.Log.Info("access application deleted successfully", "name", name, "existing", exists) return nil } + +func (c *CloudflareAPI) createAccessApplicationPolicies(accountId string, applicationId string, config networkingv1alpha1.AccessConfig) error { + ctx := context.Background() + + // If we have policies to apply + if len(config.AccessPolicies) > 0 { + + // Process policies + for precedence, policy := range config.AccessPolicies { + + // Process include rules + if len(policy.Include) > 0 { + ids, err := c.getAccessGroupIdsByNames(policy.Include) + if err != nil { + c.Log.Error(err, "failed retrieving access group ids from names", "type", "include", "groups", policy.Include) + } + policy.Include = ids + } + + // Process exclude rules + if len(policy.Exclude) > 0 { + ids, err := c.getAccessGroupIdsByNames(policy.Exclude) + if err != nil { + c.Log.Error(err, "failed retrieving access group ids from names", "type", "exclude", "groups", policy.Include) + } + policy.Exclude = ids + } + + // Process require rules + if len(policy.Require) > 0 { + ids, err := c.getAccessGroupIdsByNames(policy.Require) + if err != nil { + c.Log.Error(err, "failed retrieving access group ids from names", "type", "require", "groups", policy.Include) + } + policy.Require = ids + } + + // Check access policy exists + name := policy.Name + exists, id, err := c.getAccessPolicyIdByName(applicationId, name) + if err != nil { + c.Log.Error(err, "failed retrieving access policy by name", "policy", name) + } + + // Handle policy creation + cfPolicy := cloudflare.AccessPolicy{ + ID: applicationId, + Precedence: precedence, + Decision: policy.Action, + Name: policy.Name, + PurposeJustificationRequired: &policy.PurposeJustificationRequired, + PurposeJustificationPrompt: &policy.PurposeJustificationPrompt, + Include: wrapIdsInGroup(policy.Include), + Exclude: wrapIdsInGroup(policy.Exclude), + Require: wrapIdsInGroup(policy.Require), + } + + if exists { + cfPolicy.ID = id + c.Log.Info("updating access policy for application", "applicationId", applicationId, "policyId", id) + _, err := c.CloudflareClient.UpdateAccessPolicy(ctx, accountId, applicationId, cfPolicy) + if err != nil { + c.Log.Error(err, "error updating access policy for application", "applicationId", applicationId, "policyId", id) + return err + } + } else { + c.Log.Info("creating access policy for application", "applicationId", applicationId, "policyId", id) + cfPolicy.ID = "" + _, err := c.CloudflareClient.CreateAccessPolicy(ctx, accountId, applicationId, cfPolicy) + if err != nil { + c.Log.Error(err, "error creating access policy for application", "applicationId", applicationId, "policyId", id) + return err + } + } + } + } + c.Log.Info("access policies reconciled successfully", "applicationId", applicationId) + + return nil +} + +func wrapIdsInGroup(strings []string) []interface{} { + interfaces := make([]interface{}, len(strings)) + for i, s := range strings { + interfaces[i] = map[string]interface{}{ + "group": map[string]interface{}{ + "id": s, + }, + } + } + return interfaces +} From ebdc1de4d9a093d0dc831f6f1fee5ba600403748 Mon Sep 17 00:00:00 2001 From: Tomas Liumparas Date: Fri, 6 Dec 2024 02:04:18 +0200 Subject: [PATCH 7/9] Handle deletion of access policies --- controllers/cloudflare_api.go | 43 +++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/controllers/cloudflare_api.go b/controllers/cloudflare_api.go index b292f5d..9dd9351 100644 --- a/controllers/cloudflare_api.go +++ b/controllers/cloudflare_api.go @@ -642,6 +642,7 @@ func (c *CloudflareAPI) CreateAccessConfig(name string, config networkingv1alpha id = createdApp.ID } + // Handle application policies err = c.createAccessApplicationPolicies(accountId, id, config) if err != nil { c.Log.Error(err, "error creating access application policies", "name", name) @@ -752,6 +753,48 @@ func (c *CloudflareAPI) createAccessApplicationPolicies(accountId string, applic } } } + + // Check if there are any policies we need to delete + existingPolicies, _, err := c.CloudflareClient.AccessPolicies(ctx, accountId, applicationId, cloudflare.PaginationOptions{}) + if err != nil { + c.Log.Error(err, "failed retrieving existing access policies for application", "applicationId", applicationId) + } + + if len(existingPolicies) > 0 { + for _, policy := range existingPolicies { + + // We need to delete all existing policies + if len(config.AccessPolicies) == 0 { + c.Log.Info("deleting access policy for application", "applicationId", applicationId, "policyId", policy.ID) + + err := c.CloudflareClient.DeleteAccessPolicy(ctx, accountId, applicationId, policy.ID) + if err != nil { + c.Log.Error(err, "error deleting access policy for application", "applicationId", applicationId, "policyId", policy.ID) + return err + } + } + + // Check if the policy is still required + required := false + for _, configPolicy := range config.AccessPolicies { + if configPolicy.Name == policy.Name { + required = true + break + } + } + + // Delete since policy is not required + if !required { + err := c.CloudflareClient.DeleteAccessPolicy(ctx, accountId, applicationId, policy.ID) + if err != nil { + c.Log.Error(err, "error deleting access policy for application", "applicationId", applicationId, "policyId", policy.ID) + return err + } + } + + } + } + c.Log.Info("access policies reconciled successfully", "applicationId", applicationId) return nil From 04423e451fc92794db639d5c117270aea46e9fe0 Mon Sep 17 00:00:00 2001 From: Tomas Liumparas Date: Fri, 6 Dec 2024 02:56:56 +0200 Subject: [PATCH 8/9] Fix access policy precedence --- controllers/cloudflare_api.go | 92 +++++++++++++++++------------------ 1 file changed, 46 insertions(+), 46 deletions(-) diff --git a/controllers/cloudflare_api.go b/controllers/cloudflare_api.go index 9dd9351..75fe2b4 100644 --- a/controllers/cloudflare_api.go +++ b/controllers/cloudflare_api.go @@ -681,6 +681,47 @@ func (c *CloudflareAPI) DeleteAccessConfig(name string, config networkingv1alpha func (c *CloudflareAPI) createAccessApplicationPolicies(accountId string, applicationId string, config networkingv1alpha1.AccessConfig) error { ctx := context.Background() + // Check if there are any policies we need to delete + existingPolicies, _, err := c.CloudflareClient.AccessPolicies(ctx, accountId, applicationId, cloudflare.PaginationOptions{}) + if err != nil { + c.Log.Error(err, "failed retrieving existing access policies for application", "applicationId", applicationId) + } + + if len(existingPolicies) > 0 { + for _, policy := range existingPolicies { + + // We need to delete all existing policies + if len(config.AccessPolicies) == 0 { + c.Log.Info("deleting access policy for application", "applicationId", applicationId, "policyId", policy.ID) + + err := c.CloudflareClient.DeleteAccessPolicy(ctx, accountId, applicationId, policy.ID) + if err != nil { + c.Log.Error(err, "error deleting access policy for application", "applicationId", applicationId, "policyId", policy.ID) + return err + } + } + + // Check if the policy is still required + required := false + for _, configPolicy := range config.AccessPolicies { + if configPolicy.Name == policy.Name { + required = true + break + } + } + + // Delete since policy is not required + if !required { + err := c.CloudflareClient.DeleteAccessPolicy(ctx, accountId, applicationId, policy.ID) + if err != nil { + c.Log.Error(err, "error deleting access policy for application", "applicationId", applicationId, "policyId", policy.ID) + return err + } + } + + } + } + // If we have policies to apply if len(config.AccessPolicies) > 0 { @@ -724,7 +765,7 @@ func (c *CloudflareAPI) createAccessApplicationPolicies(accountId string, applic // Handle policy creation cfPolicy := cloudflare.AccessPolicy{ ID: applicationId, - Precedence: precedence, + Precedence: precedence + 1, Decision: policy.Action, Name: policy.Name, PurposeJustificationRequired: &policy.PurposeJustificationRequired, @@ -736,65 +777,24 @@ func (c *CloudflareAPI) createAccessApplicationPolicies(accountId string, applic if exists { cfPolicy.ID = id - c.Log.Info("updating access policy for application", "applicationId", applicationId, "policyId", id) + c.Log.Info("updating access policy for application", "applicationId", applicationId, "policyName", name, "precedence", precedence) _, err := c.CloudflareClient.UpdateAccessPolicy(ctx, accountId, applicationId, cfPolicy) if err != nil { - c.Log.Error(err, "error updating access policy for application", "applicationId", applicationId, "policyId", id) + c.Log.Error(err, "error updating access policy for application", "applicationId", applicationId, "policyName", name, "precedence", precedence) return err } } else { - c.Log.Info("creating access policy for application", "applicationId", applicationId, "policyId", id) + c.Log.Info("creating access policy for application", "applicationId", applicationId, "policyName", name, "precedence", precedence) cfPolicy.ID = "" _, err := c.CloudflareClient.CreateAccessPolicy(ctx, accountId, applicationId, cfPolicy) if err != nil { - c.Log.Error(err, "error creating access policy for application", "applicationId", applicationId, "policyId", id) + c.Log.Error(err, "error creating access policy for application", "applicationId", applicationId, "policyName", name, "precedence", precedence) return err } } } } - // Check if there are any policies we need to delete - existingPolicies, _, err := c.CloudflareClient.AccessPolicies(ctx, accountId, applicationId, cloudflare.PaginationOptions{}) - if err != nil { - c.Log.Error(err, "failed retrieving existing access policies for application", "applicationId", applicationId) - } - - if len(existingPolicies) > 0 { - for _, policy := range existingPolicies { - - // We need to delete all existing policies - if len(config.AccessPolicies) == 0 { - c.Log.Info("deleting access policy for application", "applicationId", applicationId, "policyId", policy.ID) - - err := c.CloudflareClient.DeleteAccessPolicy(ctx, accountId, applicationId, policy.ID) - if err != nil { - c.Log.Error(err, "error deleting access policy for application", "applicationId", applicationId, "policyId", policy.ID) - return err - } - } - - // Check if the policy is still required - required := false - for _, configPolicy := range config.AccessPolicies { - if configPolicy.Name == policy.Name { - required = true - break - } - } - - // Delete since policy is not required - if !required { - err := c.CloudflareClient.DeleteAccessPolicy(ctx, accountId, applicationId, policy.ID) - if err != nil { - c.Log.Error(err, "error deleting access policy for application", "applicationId", applicationId, "policyId", policy.ID) - return err - } - } - - } - } - c.Log.Info("access policies reconciled successfully", "applicationId", applicationId) return nil From 7bde824ad9e72b02a3b4970a99fc1c62b1224337 Mon Sep 17 00:00:00 2001 From: Tomas Liumparas Date: Fri, 6 Dec 2024 11:45:10 +0200 Subject: [PATCH 9/9] Cleanup unused comments --- api/v1alpha1/tunnelbinding_types.go | 28 +++++----------------------- 1 file changed, 5 insertions(+), 23 deletions(-) diff --git a/api/v1alpha1/tunnelbinding_types.go b/api/v1alpha1/tunnelbinding_types.go index d3b34e5..622fbe9 100644 --- a/api/v1alpha1/tunnelbinding_types.go +++ b/api/v1alpha1/tunnelbinding_types.go @@ -224,7 +224,7 @@ type AccessPolicy struct { // The amount of time that tokens issued for the application will be valid. Must be in the format 300ms or 2h45m. Valid time units are: ns, us (or µs), ms, s, m, h. //+kubebuilder:validation:Optional //+kubebuilder:default:="24h" - SessionDuration string `json:"sessionDuration"` + // SessionDuration string `json:"sessionDuration"` // Require users to enter a justification when they log in to the application. //+kubebuilder:validation:Optional //+kubebuilder:default:=false @@ -238,37 +238,19 @@ type AccessPolicy struct { func (c *AccessConfig) NewAccessApplication(hostname string) cloudflare.AccessApplication { return cloudflare.AccessApplication{ - // GatewayRules: []cloudflare.AccessApplicationGatewayRule{}, - AllowedIdps: c.Settings.Authentication.AllowedIdps, - CustomDenyMessage: c.Settings.Authentication.CustomDenyMessage, - LogoURL: c.Settings.Appearance.CustomLogo, - // AUD: "", + AllowedIdps: c.Settings.Authentication.AllowedIdps, + CustomDenyMessage: c.Settings.Authentication.CustomDenyMessage, + LogoURL: c.Settings.Appearance.CustomLogo, Domain: hostname, Type: cloudflare.AccessApplicationType(c.Type), SessionDuration: c.Settings.Authentication.SessionDuration, SameSiteCookieAttribute: c.Settings.Cookies.SameSiteAttribute, CustomDenyURL: c.Settings.Authentication.CustomDenyUrl, Name: hostname, - // PrivateAddress: "", - // CorsHeaders: &cloudflare.AccessApplicationCorsHeaders{ - // AllowedMethods: []string{}, - // AllowedOrigins: []string{}, - // AllowedHeaders: []string{}, - // AllowAllMethods: false, - // AllowAllHeaders: false, - // AllowAllOrigins: false, - // AllowCredentials: false, - // MaxAge: 0, - // }, - // CreatedAt: &time.Time{}, - // UpdatedAt: &time.Time{}, - // SaasApplication: &cloudflare.SaasApplication{}, - AutoRedirectToIdentity: &c.Settings.Authentication.InstantAuth, - // SkipInterstitial: new(bool), + AutoRedirectToIdentity: &c.Settings.Authentication.InstantAuth, AppLauncherVisible: &c.Settings.Appearance.AppLauncherVisibility, EnableBindingCookie: &c.Settings.Cookies.EnableBindingCookie, HttpOnlyCookieAttribute: &c.Settings.Cookies.EnableHttpOnly, - // ServiceAuth401Redirect: new(bool), } }