Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
160 changes: 160 additions & 0 deletions pkg/cloudflare-controller/tunnel-client.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ type TunnelClientInterface interface {
PutExposures(ctx context.Context, exposures []exposure.Exposure) error
TunnelDomain() string
FetchTunnelToken(ctx context.Context) (string, error)
PutAccessApplication(ctx context.Context, exposures []exposure.Exposure) error
PutAccessPolicy(ctx context.Context, exposures []exposure.Exposure) error
}

var _ TunnelClientInterface = &TunnelClient{}
Expand All @@ -41,6 +43,164 @@ func (t *TunnelClient) PutExposures(ctx context.Context, exposures []exposure.Ex
if err != nil {
return errors.Wrap(err, "update DNS CNAME record")
}

err = t.PutAccessApplication(ctx, exposures)
if err != nil {
return errors.Wrap(err, "put access application")
}

err = t.PutAccessPolicy(ctx, exposures)
if err != nil {
return errors.Wrap(err, "put access policy")
}

return nil
}

func (t *TunnelClient) PutAccessApplication(ctx context.Context, exposures []exposure.Exposure) error {
apps, _, err := t.cfClient.ListAccessApplications(ctx, cloudflare.ResourceIdentifier(t.accountId), cloudflare.ListAccessApplicationsParams{})
if err != nil {
return errors.Wrap(err, "list access applications")
}

for _, exposure := range exposures {
if exposure.AccessApplicationName == nil {
continue
}

appName := *exposure.AccessApplicationName
appDomain := exposure.Hostname

var existingApp *cloudflare.AccessApplication
for _, app := range apps {
if app.Name == appName {
existingApp = &app
break
}
}

if exposure.IsDeleted {
if existingApp != nil {
t.logger.Info("delete access application", "app", existingApp.Name)
err := t.cfClient.DeleteAccessApplication(ctx, cloudflare.ResourceIdentifier(t.accountId), existingApp.ID)
if err != nil {
return errors.Wrap(err, "delete access application")
}
}
continue
}

if existingApp != nil {
t.logger.Info("update access application", "app", existingApp.Name)
_, err := t.cfClient.UpdateAccessApplication(ctx, cloudflare.ResourceIdentifier(t.accountId), cloudflare.UpdateAccessApplicationParams{
ID: existingApp.ID,
Name: appName,
Domain: appDomain,
})
if err != nil {
return errors.Wrap(err, "update access application")
}
} else {
t.logger.Info("create access application", "app", appName)
_, err := t.cfClient.CreateAccessApplication(ctx, cloudflare.ResourceIdentifier(t.accountId), cloudflare.CreateAccessApplicationParams{
Name: appName,
Domain: appDomain,
})
if err != nil {
return errors.Wrap(err, "create access application")
}
}
}

return nil
}

func (t *TunnelClient) PutAccessPolicy(ctx context.Context, exposures []exposure.Exposure) error {
apps, _, err := t.cfClient.ListAccessApplications(ctx, cloudflare.ResourceIdentifier(t.accountId), cloudflare.ListAccessApplicationsParams{})
if err != nil {
return errors.Wrap(err, "list access applications")
}

for _, exposure := range exposures {
if exposure.AccessApplicationName == nil || len(exposure.AccessPolicyAllowedEmails) == 0 {
continue
}

appName := *exposure.AccessApplicationName
allowedEmails := exposure.AccessPolicyAllowedEmails

var app *cloudflare.AccessApplication
for _, a := range apps {
if a.Name == appName {
app = &a
break
}
}

if app == nil {
continue
}

policies, _, err := t.cfClient.ListAccessPolicies(ctx, cloudflare.ResourceIdentifier(t.accountId), cloudflare.ListAccessPoliciesParams{
ApplicationID: app.ID,
})
if err != nil {
return errors.Wrap(err, "list access policies")
}

var existingPolicy *cloudflare.AccessPolicy
for _, p := range policies {
if p.Name == "Allow predefined emails" {
existingPolicy = &p
break
}
}

if exposure.IsDeleted {
if existingPolicy != nil {
t.logger.Info("delete access policy", "policy", existingPolicy.Name)
err := t.cfClient.DeleteAccessPolicy(ctx, cloudflare.ResourceIdentifier(t.accountId), cloudflare.DeleteAccessPolicyParams{
ApplicationID: app.ID,
PolicyID: existingPolicy.ID,
})
if err != nil {
return errors.Wrap(err, "delete access policy")
}
}
continue
}

var include []interface{}
for _, email := range allowedEmails {
include = append(include, map[string]interface{}{"email": map[string]string{"email": email}})
}

if existingPolicy != nil {
t.logger.Info("update access policy", "policy", existingPolicy.Name)
_, err := t.cfClient.UpdateAccessPolicy(ctx, cloudflare.ResourceIdentifier(t.accountId), cloudflare.UpdateAccessPolicyParams{
ApplicationID: app.ID,
PolicyID: existingPolicy.ID,
Name: "Allow predefined emails",
Decision: "allow",
Include: include,
})
if err != nil {
return errors.Wrap(err, "update access policy")
}
} else {
t.logger.Info("create access policy", "policy", "Allow predefined emails")
_, err := t.cfClient.CreateAccessPolicy(ctx, cloudflare.ResourceIdentifier(t.accountId), cloudflare.CreateAccessPolicyParams{
ApplicationID: app.ID,
Name: "Allow predefined emails",
Decision: "allow",
Include: include,
})
if err != nil {
return errors.Wrap(err, "create access policy")
}
}
}

return nil
}

Expand Down
23 changes: 21 additions & 2 deletions pkg/controller/transform.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package controller
import (
"context"
"fmt"
"strings"

"sigs.k8s.io/controller-runtime/pkg/client"

Expand All @@ -15,6 +16,8 @@ import (
"k8s.io/utils/ptr"
)

var DefaultAccessPolicyAllowedEmails = []string{"@thehutgroup.com", "@thg.com", "@thgingenuity.com"}

func FromIngressToExposure(ctx context.Context, logger logr.Logger, kubeClient client.Client, ingress networkingv1.Ingress) ([]exposure.Exposure, error) {
isDeleted := false

Expand Down Expand Up @@ -51,6 +54,20 @@ func FromIngressToExposure(ctx context.Context, logger logr.Logger, kubeClient c
originServerName = ptr.To(name)
}

var accessApplicationName *string
if name, ok := getAnnotation(ingress.Annotations, AnnotationAccessApplicationName); ok {
accessApplicationName = ptr.To(name)
} else {
accessApplicationName = ptr.To(ingress.GetName())
}

var accessPolicyAllowedEmails []string
if emails, ok := getAnnotation(ingress.Annotations, AnnotationAccessPolicyAllowedEmails); ok {
accessPolicyAllowedEmails = strings.Split(emails, ",")
} else {
accessPolicyAllowedEmails = DefaultAccessPolicyAllowedEmails
}

var proxySSLVerifyEnabled *bool

if proxySSLVerify, ok := getAnnotation(ingress.Annotations, AnnotationProxySSLVerify); ok {
Expand Down Expand Up @@ -114,8 +131,10 @@ func FromIngressToExposure(ctx context.Context, logger logr.Logger, kubeClient c
PathPrefix: path.Path,
IsDeleted: isDeleted,
ProxySSLVerifyEnabled: proxySSLVerifyEnabled,
HTTPHostHeader: httpHostHeader,
OriginServerName: originServerName,
HTTPHostHeader: httpHostHeader,
OriginServerName: originServerName,
AccessApplicationName: accessApplicationName,
AccessPolicyAllowedEmails: accessPolicyAllowedEmails,
})
}
}
Expand Down
Loading