Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Integrate tailscale into k3s #7352

Merged
merged 1 commit into from
Jun 9, 2023

Conversation

manuelbuil
Copy link
Contributor

@manuelbuil manuelbuil commented Apr 26, 2023

Proposed Changes

This PR adds the necessary code to integrate VPNs into k3s and incorporates tailscale as the first VPN option.
It also includes an ADR that explains the change

Types of Changes

New feature

Verification

1 - Install tailscale in the node
2 - Create a key in tailscale
3 - Deploy k3s with the flag:

vpn-auth: "name=tailscale,joinKey=$CREATED_KEY"

4 - Verify that:
a) Tailscale interface exists and has an IP
b) kubectl get endpoints is using the tailscale IP
c) kubectl get nodes -o wide shows the tailscale IP as the nodeIP

Testing

Linked Issues

#7353

User-Facing Change

Integration of tailscale VPN into k3s

Further Comments

@manuelbuil manuelbuil requested a review from a team as a code owner April 26, 2023 07:22
@manuelbuil manuelbuil force-pushed the vpnintegrations-afterparental branch from cfc3a7b to 622c8fa Compare April 26, 2023 09:51
@manuelbuil manuelbuil force-pushed the vpnintegrations-afterparental branch 2 times, most recently from d938d1e to b0668be Compare April 26, 2023 14:33
@manuelbuil manuelbuil requested a review from brandond April 26, 2023 16:07
pkg/netutil/vpn.go Outdated Show resolved Hide resolved
@manuelbuil manuelbuil force-pushed the vpnintegrations-afterparental branch from b0668be to 023514f Compare April 26, 2023 16:54
@manuelbuil manuelbuil requested a review from matttrach April 26, 2023 19:42
pkg/cli/cmds/agent.go Outdated Show resolved Hide resolved
pkg/netutil/vpn.go Outdated Show resolved Hide resolved
pkg/netutil/vpn.go Outdated Show resolved Hide resolved
pkg/netutil/vpn.go Outdated Show resolved Hide resolved
@manuelbuil manuelbuil force-pushed the vpnintegrations-afterparental branch from 023514f to 2a8954a Compare April 27, 2023 13:12
@cwayne18
Copy link
Member

Can we get either an integration or an e2e test for this?

@nikolaishields
Copy link
Contributor

nikolaishields commented Apr 28, 2023

From a community perspective it would be really nice to include the ability to setup a tailscale funnel at node deploy time to allow public access to the node at a targeted port or service address. This would come in particularly convenient in situations where public ingress is desired. A few example use-cases:

  • IDP (keycloak, vault, ory, dex) is deployed to the cluster by the k3s helm-manifest controller at cluster deploy. This would allow k3s users to have SSO access to their clusters immediately after bootstrap.
  • Traefik ingress is proxying public services, yet the cluster is behind CGNAT and ports cannot be exposed.
  • HTTP01 cert-generation via public exposure of the ACME endpoint

Perhaps a Kubernetes ingress integration could even be designed that instantiates a funnel as a CRD when ingress is created. The sky's the limit.
Special thanks to @manuelbuil for drafting this as it looks awesome!

@manuelbuil manuelbuil force-pushed the vpnintegrations-afterparental branch from 2a8954a to 60ce4b9 Compare May 1, 2023 13:52
@codecov
Copy link

codecov bot commented May 1, 2023

Codecov Report

Patch coverage has no change and project coverage change: -28.27 ⚠️

Comparison is base (e5e1a67) 47.55% compared to head (023514f) 19.29%.

❗ Current head 023514f differs from pull request most recent head 869e030. Consider uploading reports for the commit 869e030 to get more accurate results

Additional details and impacted files
@@             Coverage Diff             @@
##           master    #7352       +/-   ##
===========================================
- Coverage   47.55%   19.29%   -28.27%     
===========================================
  Files         141       81       -60     
  Lines       14347     5463     -8884     
===========================================
- Hits         6823     1054     -5769     
+ Misses       6442     4187     -2255     
+ Partials     1082      222      -860     
Flag Coverage Δ
inttests ?
unittests 19.29% <0.00%> (-0.58%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Impacted Files Coverage Δ
pkg/agent/flannel/setup.go 0.00% <0.00%> (-41.14%) ⬇️
pkg/cli/cmds/agent.go 0.00% <0.00%> (-100.00%) ⬇️
pkg/cli/cmds/server.go 0.00% <ø> (-100.00%) ⬇️
pkg/daemons/config/types.go 67.21% <ø> (-17.24%) ⬇️

... and 137 files with indirect coverage changes

☔ View full report in Codecov by Sentry.
📢 Do you have feedback about the report comment? Let us know in this issue.

@manuelbuil manuelbuil force-pushed the vpnintegrations-afterparental branch from 60ce4b9 to c75499d Compare May 1, 2023 15:04
@manuelbuil
Copy link
Contributor Author

From a community perspective it would be really nice to include the ability to setup a tailscale funnel at node deploy time to allow public access to the node at a targeted port or service address. This would come in particularly convenient in situations where public ingress is desired. A few example use-cases:

  • IDP (keycloak, vault, ory, dex) is deployed to the cluster by the k3s helm-manifest controller at cluster deploy. This would allow k3s users to have SSO access to their clusters immediately after bootstrap.
  • Traefik ingress is proxying public services, yet the cluster is behind CGNAT and ports cannot be exposed.
  • HTTP01 cert-generation via public exposure of the ACME endpoint

Perhaps a Kubernetes ingress integration could even be designed that instantiates a funnel as a CRD when ingress is created. The sky's the limit. Special thanks to @manuelbuil for drafting this as it looks awesome!

Hey @nikolaishields, how are you? Thanks for your suggestion :). There are several cool features that tailscale provides, such as funneling, but I decided to keep them outside of the integration (at least for the time being). I want to keep it dead simple and check what is the community reaction, typical bugs, etc before taking the next iteration.

The CRD+ingress idea could be cool. I guess you mean having the ingress service as a nodePort service and offering that port with tailscale funnel. Kind of a "type: loadbalancer" service but without the loadbalancer, right? Wouldn't klipper-lb fulfill that use case too? Now I want/need to test it

@manuelbuil
Copy link
Contributor Author

Can we get either an integration or an e2e test for this?

Yep, on it

@nikolaishields
Copy link
Contributor

Hey @nikolaishields, how are you?

I'm doing well thanks for asking!

There are several cool features that tailscale provides, such as funneling, but I decided to keep them outside of the integration (at least for the time being). I want to keep it dead simple and check what is the community reaction, typical bugs, etc before taking the next iteration.

Makes perfect sense, if you ever want/need a hand on implementing any of these features let me know. Happy to help!

The CRD+ingress idea could be cool. I guess you mean having the ingress service as a nodePort service and offering that port with tailscale funnel. Kind of a "type: loadbalancer" service but without the loadbalancer, right? Wouldn't klipper-lb fulfill that use case too? Now I want/need to test it

It probably could and then a user could just run a funnel against the node port. In theory I suppose one could also just funnel a service directly via local dns lookup (e.g. tailscale serve <servicename>.default.cluster.local:<port number>).

@manuelbuil
Copy link
Contributor Author

Can we get either an integration or an e2e test for this?

Actually, could we merge this PR and then work on the e2e test? It complicates things that the code is not yet part of upstream k3s

@manuelbuil manuelbuil requested a review from brandond May 2, 2023 13:39
@brandond brandond dismissed their stale review May 2, 2023 21:55

stale

@brandond
Copy link
Member

brandond commented May 2, 2023

I would defer to @cwayne18 on merging without tests. I think @dereknola might have some suggestions on how to add tests before the PR is merged? I have had some frustrations with that process as well.

@manuelbuil manuelbuil force-pushed the vpnintegrations-afterparental branch 2 times, most recently from 21ab427 to a6e0809 Compare May 4, 2023 14:55
@manuelbuil
Copy link
Contributor Author

I would defer to @cwayne18 on merging without tests. I think @dereknola might have some suggestions on how to add tests before the PR is merged? I have had some frustrations with that process as well.

I am adding them in the end

@manuelbuil manuelbuil force-pushed the vpnintegrations-afterparental branch from a6e0809 to 4d285a4 Compare May 5, 2023 08:58
@manuelbuil manuelbuil force-pushed the vpnintegrations-afterparental branch from b429d71 to 3d8540c Compare June 1, 2023 07:02
@manuelbuil manuelbuil requested a review from rbrtbnfgl June 1, 2023 15:26
@manuelbuil manuelbuil force-pushed the vpnintegrations-afterparental branch from 3d8540c to 83c51d1 Compare June 6, 2023 06:30
@manuelbuil manuelbuil force-pushed the vpnintegrations-afterparental branch 7 times, most recently from fab47fc to f171993 Compare June 8, 2023 16:41
Comment on lines 10 to 19
var out bytes.Buffer

cmd := exec.Command(command, args...)
cmd.Stdout = &out
err := cmd.Run()
if err != nil {
return "", err
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With this, you get more info if there is an error, the err outs from exec are almost always just exit 1/2, which isn't helpful.

Suggested change
var out bytes.Buffer
cmd := exec.Command(command, args...)
cmd.Stdout = &out
err := cmd.Run()
if err != nil {
return "", err
}
var out, errOut bytes.Buffer
cmd := exec.Command(command, args...)
cmd.Stdout = &out
cmd.Stderr = &errOut
err := cmd.Run()
if err != nil {
return errOut.String(), err
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice, thanks for that. The user of this function should then be aware that the interesting error is the string returned and not the error, right? I'll add a comment

pkg/util/file.go Outdated
return "", nil
}

for {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would suggest adding a failure state is after 5min or something, the path still does not exist.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, good point, let me replace the infinite loop with something else using time. I copied/pasted this function from another place though, and it never happened before, but better safe than sorry

@@ -11,3 +14,21 @@ func SetFileModeForPath(name string, mode os.FileMode) error {
func SetFileModeForFile(file *os.File, mode os.FileMode) error {
return nil
}

// ReadFile reads from a file
func ReadFile(path string) (string, error) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment as in the linux version

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand why this is OS-specific, there doesn't appear to be anything in either function that would differ across platforms.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could remove it in another PR (just in case we need to revert) and see if things are still working

Comment on lines 19 to 23
node.vm.network "private_network",
:ip => node_ip4,
:netmask => "255.255.255.0",
:libvirt__dhcp_enabled => false,
:libvirt__forward_mode => "none"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need the special libvirt flags? Its not dualstack

Suggested change
node.vm.network "private_network",
:ip => node_ip4,
:netmask => "255.255.255.0",
:libvirt__dhcp_enabled => false,
:libvirt__forward_mode => "none"
vm.network "private_network", ip: node_ip, netmask: "255.255.255.0"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh, I think we are using those flags in all the networking tests. We should remove it when not needed then. Thanks!

@@ -0,0 +1,76 @@
# Integrate vpn in k3s
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit on the filename - should it be integrate-vpns.md? I'm not sure why the word was truncated.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good catch, probably my mistake

@@ -151,6 +153,18 @@ var (
Usage: "(agent/networking) Override default flannel cni config file",
Destination: &AgentConfig.FlannelCniConfFile,
}
VPNAuth = &cli.StringFlag{
Name: "vpn-auth",
Usage: "(agent/networking) (experimental) Credentials for the VPN provider. It must include the provider name and join key in the format name=tailscale,joinKey=<key>",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: be consistent with the format for vpn-auth-file

Suggested change
Usage: "(agent/networking) (experimental) Credentials for the VPN provider. It must include the provider name and join key in the format name=tailscale,joinKey=<key>",
Usage: "(agent/networking) (experimental) Credentials for the VPN provider. It must include the provider name and join key in the format name=<vpn-provider>,joinKey=<key>",

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

pkg/util/file.go Show resolved Hide resolved
@@ -11,3 +14,21 @@ func SetFileModeForPath(name string, mode os.FileMode) error {
func SetFileModeForFile(file *os.File, mode os.FileMode) error {
return nil
}

// ReadFile reads from a file
func ReadFile(path string) (string, error) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand why this is OS-specific, there doesn't appear to be anything in either function that would differ across platforms.

@manuelbuil manuelbuil force-pushed the vpnintegrations-afterparental branch from f171993 to 6147c87 Compare June 9, 2023 07:20
@manuelbuil manuelbuil requested review from brandond and dereknola June 9, 2023 07:20
Signed-off-by: Manuel Buil <mbuil@suse.com>
@manuelbuil manuelbuil force-pushed the vpnintegrations-afterparental branch from 6147c87 to 869e030 Compare June 9, 2023 10:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants