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

Add Windows Service support for TCP tunnel #479

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

ZetaTwo
Copy link
Contributor

@ZetaTwo ZetaTwo commented Dec 12, 2024

Summary

Adds support for running Pomerium CLI TCP tunnel as a Windows service.
When built for Windows, this adds two new modes for the TCP tunnel.
When executed as a Windows service, it will setup the appropriate event loop to work as a windows service.
When launched from the CLI with the flag -service it will run as if it was running as a service to allow for debugging service scenarios.

This allows for an idiomatic way to run a long-lived Pomerium CLI session. Naturally, if it is run as a system service it won't be able to launch a browser to sign-in so it probably only makes sense for routes that are unauthenticated.

The motivating use-case for this is to allow access to a service, behind a Pomerium server that requires mTLS but no further authentication, to clients that don't implement client cert authentication.

It does not change any existing functionality, neither on Windows or other platforms.

Code structure

First we move almost all code from tcp.go to tcp_impl.go.
Then we make a slight refactoring to create the context outside the runTcp function and pass it as an argument instead.
Then we break out the init function and Cobra command definition to platform specific files.
The non-Windows one is essentially unchanged, except that move back the common parts of init to tcp_impl.go.
The Windows variant adds code for handling the service and service emulation modes.

Related issues

None

Checklist

  • reference any related issues
  • updated docs
  • updated unit tests
  • updated UPGRADING.md
  • add appropriate tag (improvement / bug / etc)
  • ready for review

@ZetaTwo ZetaTwo requested a review from a team as a code owner December 12, 2024 16:41
@ZetaTwo ZetaTwo requested a review from wrmedford December 12, 2024 16:41
@calebdoxsey calebdoxsey self-requested a review December 12, 2024 19:18
@calebdoxsey
Copy link
Contributor

Thanks for the contribution. I will test this on a windows machine.

I am wondering if there's a more generic way of doing this so that the service handling isn't TCP-specific? Possibly we could run the service handling in cobra's PersistentPreRunE? I ask this because we're adding UDP tunneling support which is very similar but a separate subcommand.

I really appreciate the work you've done here, so if you don't have the bandwidth for this or what I'm suggesting doesn't actually make sense, we could use this PR as a first step and we can iterate from there.

@calebdoxsey
Copy link
Contributor

I tested this and it seems to work, albeit, as mentioned, only on an unauthenticated route.

@ZetaTwo
Copy link
Contributor Author

ZetaTwo commented Dec 13, 2024

I am wondering if there's a more generic way of doing this so that the service handling isn't TCP-specific? Possibly we could run the service handling in cobra's PersistentPreRunE? I ask this because we're adding UDP tunneling support which is very similar but a separate subcommand.

I agree that it would make more sense to make this more generic. The reason I didn't was that I wanted to make the initial change as small as possible to make it more digestible and I also was not familiar with Cobra so I didn't know how to structure it.

If you like this change and would like to make it more generic I can look into that.

@ZetaTwo
Copy link
Contributor Author

ZetaTwo commented Dec 13, 2024

Ok, so the way I envision the structure is that you have a something, for example PersistentPreRun that creates a wrapper function which either executes the command normally or as a service. This wrapper function is then called from each specific command with the implementation of the command as a function pointer. So something like (pseudocode):

func createWrapper() {
  if serviceMode {
    wrapper = runInServiceMode
  } elif serviceDebugMode {
    wrapper = runInServiceDebugMode
  } else {
    wrapper = runCli
  }

  return wrapper
}


func tcpCommand() {
  wrapper(tcpCommand)
}

func udpCommand() {
  wrapper(udpCommand)
}

func runInServiceMode(commandFunction) {
  service setup stuff
  commandFunction(args, ...)
  service event loop
}

func runInServiceMode(commandFunction) {
  service debug setup stuff
  commandFunction(args, ...)
  service event loop
}

func runInServiceMode(commandFunction) {
  commandFunction(args, ...)
}

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.

2 participants