adyanth / cloudflare-operator

A Kubernetes Operator to create and manage Cloudflare Tunnels and DNS records for (HTTP/TCP/UDP*) Service Resources
https://adyanth.site/posts/migration-compose-k8s/cloudflare-tunnel-operator-architecture/
Apache License 2.0
357 stars 37 forks source link

Zero Trust Applications support #67

Open tomasliumparas opened 2 years ago

tomasliumparas commented 2 years ago

Hey I was thinking what would be you thoughts about managing Zero Trust Applications too?

Creating/Assigning access policies?

Would you consider a PR?

adyanth commented 2 years ago

Yes, I was planning on doing that once I could get around to finishing #43, which would need to create/access ZT applications.

I was thinking another CRD for ZT, which can attach to a TunnelBinding (WIP in #51/#63), creating and managing apps for the services referred there. When #43 comes in, the same CRD could attach at the other end to access the apps exposed via ZT.

What are your thoughts and structure for the same? Would love to hear them. I would consider a PR too, but let us hash out the details before you can start working on it to not step on each other.

tomasliumparas commented 2 years ago

Cool! So initially I was thinking as separate CRD called for Example AccessApplication.

As far as I understand AccessApplication does not need to be tied to tunnel in any way? At least from Cloudflare point in the UI there are no relations between application and tunnel?

I am thinking about a controller which would reconcile AccessApplication CRDs itself and annotation support on services but in a bit different way - another controller watches the services and creates the AccessApplication CR which then gets reconciled.

That way there is still some basic annotations support, whole logic is inside AccessApplication controller and if some more advanced functionality is needed it can be implemented via AccessApplication CR.

adyanth commented 2 years ago

I am planning to move away from service annotations in #63 which is why I wanted the "AccessApplication" resource to map to the TunnelBinding object to apply the access policies to all the services listed in the TunnelBinding. So, just one controller is enough to reconcile the new CR.

Or if separation was needed, we would need two, AccessApplication and AccessApplicationBinding to map it to services.

tomasliumparas commented 2 years ago

Okay, that makes sense

tomasliumparas commented 2 years ago

What about if we could extend TunnelBindingSubject to accompany fields needed for ZT Access App instead of TunnelBinding? That way each service exposed would have an ability to have its own ZT Access App?

And maybe add an the same struct to TunnelBinding that way there would be a possibility either to create a single AccessApp for all services inside the tunnel or different for each of them?

adyanth commented 2 years ago

Yes, that is exactly what I was thinking. That would be good to have the option of same per tunnel binding or per subject to override.

If that is the case, we need to evaluate the need/entries in AccessApplication, since the TunnelBinding controller itself can perform the reconciliation for applications too.

tomasliumparas commented 2 years ago

I came with this after looking into API docs:

type TunnelBinding struct {
    metav1.TypeMeta   `json:",inline"`
    metav1.ObjectMeta `json:"metadata,omitempty"`

    Subjects  []TunnelBindingSubject `json:"subjects"`
    TunnelRef TunnelRef              `json:"tunnelRef"`
    AccessApp AccessApp
    Status    TunnelBindingStatus `json:"status"`
}

type AccessApp struct {
    Name   string
    Domain string
    // Application type self_hosted,saas
    Type string
    // List of access policies
    AccessPolicies []AccessPolicy
    // Application settings
    Settings AccessAppSettings
}

type AccessAppSettings struct {
    // Authentication settins
    Authentication AccessAppAuthentication
    // Appearance settins
    Appearance AccessAppAppearance
    // Cookie settings
    Cookies AccessAppCookies
    // Additional settings
    Additional AccessAppAdditional
}

type AccessAppAuthentication struct {
    // The list of identiy providers which application is allowed to use. If empty all idps are allowed
    AllowedIdps []string
    // Skip identity provider selection if only one is configured
    InstantAuth bool
    // 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.
    SessionDuration string
}

type AccessAppAppearance struct {
    // Wether to show app in the launcher. Defaults to true.
    AppLauncherVisibility bool
    // Custom logo url
    CustomLogo string
}

type AccessAppCookies struct {
    // Sets the SameSite cookie setting, which provides increased security against CSRF attacks. [none,strict,lax]
    SameSiteAttribute string
    // Enables the HttpOnly cookie attribute, which increases security against XSS attacks.
    EnableHttpOnly bool
    // Protects against stolen authorization tokens. Do not use for non-HTTP applications that rely on protocols like SSH and RDP.
    EnableBindingCookie bool
}

type AccessAppAdditional struct {
    // Cloudflare will render an SSH terminal or VNC session for this application in a web browser. [ssh,vnc]
    BrowserRendering string
}

type AccessPolicy struct {
    Name    string
    Action  string
    Include []string
    Exclude []string
    Require []string
}
adyanth commented 2 years ago

The structure looks good, looks like it covers all the settings from ZT.

The TunnelBinding, if it contains the AccessApp, will not have a domain, since it changes for each app underneath, right? I would assume that the domain will be the same as the domain served by the tunnel, so that should not even be an option for the user (that would add a case where the tunnel serves a.domain.com and ZT protects b.domain.com doing nothing).

And this would also be part of the TunnelBindingSubject. This will be reconciled once the DNS record is created pointing to the tunnel, and teared down before DNS. Should be straightforward.

Thinking out loud, the lifetime of the access application is indeed the TunnelBinding object, so we do not need another controller to implement this, do correct me if I am wrong in this.

If you are planning to implement this, do allow me a couple of weeks to finish up #63 with the existing changes, so that you can build on top of that.

tomasliumparas commented 2 years ago

The reason I added it was cause CF API expects that to be in subdomain.domain.com/path /path being optional and subdomain being optional. So I thought it would make more sense to make it optional and let the user override it if needed.

Some use cases

AccessApp is set on the TunnelBinding

You have different TunnelBindingSubjects configured under it:

What could be the Domain then? *.somedomain.com? You might want to set the domain to:

AccessApp is set on TunnelBindingSubject

Lets say TunnelBindingSubject is app.dev.domain.com You might want to configure:

adyanth commented 2 years ago

In the first use case, domain will be the domain configured for the Tunnel/ClusterTunnel, so domain.tld. I do not think you can just register a subdomain on Cloudflare, can you?

In the second use case, yes, those seem valid, so we could split it and allow user to enter the path. I still do not see a reason for the user to enter the domain, if it is not being served by the tunnel.

What would the use case look like for the tunnel serving app.domain.com, but the ZT to point to something.domain.com? ZT needs the domain to already point to something for it to work right?

*.sub.domain.tld does not seem right to be configured from the controller. If another app also adds the same policy, there might be a conflict. Allowing each to only manage their own subdomain prevents conflicts, and path is a good use case to include for users to customize.

tomasliumparas commented 2 years ago

In the first use case, domain will be the domain configured for the Tunnel/ClusterTunnel, so domain.tld. I do not think you can just register a subdomain on Cloudflare, can you?

This thought was about the cases when you have some different TunBindSubjs configured under the same TunBind. Lets say you have two microservices app1.dev.domain.tld and app1.dev.domain.tld and you would like only single AccessApplication protecting them both dev.domain.tld

My idea was if you specify AccessApp at TunBind level, you should get one that is like an umbrella for all and if you specify it under TunBindSubj level, you are getting well a single one for that subject.

But that might be overthinking things :)

In the second use case, yes, those seem valid, so we could split it and allow user to enter the path. I still do not see a reason for the user to enter the domain, if it is not being served by the tunnel.

Yeah, so I thought initially either to add Path or let the user override if needed.

What would the use case look like for the tunnel serving app.domain.com, but the ZT to point to something.domain.com? ZT needs the domain to already point to something for it to work right?

Yep, I feel the same

tomasliumparas commented 2 years ago

Regarding the separate controller/Extending TunnelBinding controller

I am thinking can there be cases where you would like to configure only access applications without tunnels? For example maybe you have some other tunnels which are not managed by cloudflare-operator, like some tunnels on vm's, like bastion hosts or something like that?

But I also agree it makes sense to make it a part of TunnelBinding.

adyanth commented 2 years ago

A single ZT protecting both app1 and app2, is that needed to be combined? User can specify policies at the TunnelBinding level and we can create two applications. Doing a single one might case issues when the services are deleted/modified, no?

Managing ZT for tunnels not managed by this controller might cause the same issues like the tunnel being deleted but us trying to configure ZT causing failures.

Other than that, I think we are on the same page :)

tomasliumparas commented 2 years ago

Doing a single one might case issues when the services are deleted/modified, no?

Yeah, that might be correct.

So TunnelBinding level AccessApp would act as some default settings for all the Subjects?

I think I am okay with that :)

adyanth commented 1 year ago

Hey @tomasliumparas I merged #63 yesterday. You should be good to base off of the tunnel binding to add these! Thanks again for your interest and contribution.

Edit: Please use AccessConfig instead of AccessApp since that makes more sense naming wise.

adyanth commented 1 year ago

The below operator seems to implement this feature.

https://github.com/BojanZelic/cloudflare-zero-trust-operator

thedjpetersen commented 11 months ago

If we want some tunnels to leverage zero trust features and others to not do is it best to use both operators?

tomasliumparas commented 10 months ago

@adyanth Hey, thanks for update, will check it out!

stiliajohny commented 1 month ago

out of interest any traction on that ?

adyanth commented 1 month ago

Nothing from me. I have let this coast along for now, since it perfectly meets my use case and currently do not have plans (or time) implementing it myself. Although I'll be more than happy to review ideas and PRs!

tomasliumparas commented 3 weeks ago

Hey @adyanth,

The need for this appeared again for me. I am planning on making a PR based on the above in the upcoming 2-3weeks. I reviewed the cloudflare-zero-trust-operator, but for me it makes more sense to have a single operator which handles both tunnels and apps.