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

Support (m)TLS API Socket #24601

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
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
5 changes: 4 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -682,7 +682,10 @@ ginkgo:

.PHONY: ginkgo-remote
ginkgo-remote:
$(MAKE) ginkgo-run TAGS="$(REMOTETAGS) remote_testing"
$(MAKE) ginkgo-run TAGS="$(REMOTETAGS) remote_testing remote_unix_testing"
$(MAKE) ginkgo-run TAGS="$(REMOTETAGS) remote_testing remote_tcp_testing"
$(MAKE) ginkgo-run TAGS="$(REMOTETAGS) remote_testing remote_tls_testing"
$(MAKE) ginkgo-run TAGS="$(REMOTETAGS) remote_testing remote_mtls_testing"
Comment on lines +685 to +688
Copy link
Member

Choose a reason for hiding this comment

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

This is not acceptable to me, it is certainly great to force coverage. But we run this many times on each Pr. A 4x time increase is not acceptable.

What we can consider is some split testing, we run the test on fedora rawhide, 41, 40 and debian sid so technically would could wire this up in CI ro run each case on a different distro to not add any new overhead will still getting full coverage. The transport layer should certainly not care about the distro (except underlying kernel bugs of course) so I think that may be best option.

Copy link
Author

Choose a reason for hiding this comment

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

I agree, this was a "nuclear option" in order to get tests to run with the least amount of change to the tests themselves, and to make sure that I wasn't missing anything.

One thought I had in the interim was that ginkgo allows tagging tests and sets of tests, and selecting a subset of them on the command line. Using this, it would be possible to run a full remote tests via unix sockets, and then a subset of all tests against tcp, tls, and mtls. It would then also be possible to add a separate target to run all tests in all remotes, but wouldn't be run in CI, only on-demand in development environments.

The main thing I would need for this is guidance from the podman core team on which tests they believed were critical to be tested over every possible remote, and which are "good enough" to only be tested over unix. For example, the attach endpoints would definitely need to be tested on all different remotes, as that's how I discovered the additional fixes I had to make.


.PHONY: testbindings
# bindings tests need access to podman-registry
Expand Down
24 changes: 24 additions & 0 deletions cmd/podman/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,9 @@ func readRemoteCliFlags(cmd *cobra.Command, podmanConfig *entities.PodmanConfig)
}
podmanConfig.URI = con.URI
podmanConfig.Identity = con.Identity
podmanConfig.TLSCertFile = con.TLSCertFile
podmanConfig.TLSKeyFile = con.TLSKeyFile
podmanConfig.TLSCAFile = con.TLSCAFile
podmanConfig.MachineMode = con.IsMachine
case url.Changed:
podmanConfig.URI = url.Value.String()
Expand All @@ -174,6 +177,9 @@ func readRemoteCliFlags(cmd *cobra.Command, podmanConfig *entities.PodmanConfig)
}
podmanConfig.URI = con.URI
podmanConfig.Identity = con.Identity
podmanConfig.TLSCertFile = con.TLSCertFile
podmanConfig.TLSKeyFile = con.TLSKeyFile
podmanConfig.TLSCAFile = con.TLSCAFile
podmanConfig.MachineMode = con.IsMachine
}
case host.Changed:
Expand Down Expand Up @@ -208,6 +214,9 @@ func setupRemoteConnection(podmanConfig *entities.PodmanConfig) string {
}
podmanConfig.URI = con.URI
podmanConfig.Identity = con.Identity
podmanConfig.TLSCertFile = con.TLSCertFile
podmanConfig.TLSKeyFile = con.TLSKeyFile
podmanConfig.TLSCAFile = con.TLSCAFile
podmanConfig.MachineMode = con.IsMachine
return con.Name
case hostEnv != "":
Expand All @@ -220,6 +229,9 @@ func setupRemoteConnection(podmanConfig *entities.PodmanConfig) string {
if err == nil {
podmanConfig.URI = con.URI
podmanConfig.Identity = con.Identity
podmanConfig.TLSCertFile = con.TLSCertFile
podmanConfig.TLSKeyFile = con.TLSKeyFile
podmanConfig.TLSCAFile = con.TLSCAFile
podmanConfig.MachineMode = con.IsMachine
return con.Name
}
Expand Down Expand Up @@ -505,6 +517,18 @@ func rootFlags(cmd *cobra.Command, podmanConfig *entities.PodmanConfig) {
lFlags.StringVar(&podmanConfig.Identity, identityFlagName, podmanConfig.Identity, "path to SSH identity file, (CONTAINER_SSHKEY)")
_ = cmd.RegisterFlagCompletionFunc(identityFlagName, completion.AutocompleteDefault)

tlsCertFileFlagName := "tls-cert"
lFlags.StringVar(&podmanConfig.TLSCertFile, tlsCertFileFlagName, podmanConfig.TLSCertFile, "path to TLS client certificate PEM file for remote, (CONTAINER_TLS_CERT)")
_ = cmd.RegisterFlagCompletionFunc(tlsCertFileFlagName, completion.AutocompleteDefault)

tlsKeyFileFlagName := "tls-key"
lFlags.StringVar(&podmanConfig.TLSKeyFile, tlsKeyFileFlagName, podmanConfig.TLSKeyFile, "path to TLS client certificate private key PEM file for remote, (CONTAINER_TLS_KEY)")
_ = cmd.RegisterFlagCompletionFunc(tlsKeyFileFlagName, completion.AutocompleteDefault)

tlsCAFileFlagName := "tls-ca"
lFlags.StringVar(&podmanConfig.TLSCAFile, tlsCAFileFlagName, podmanConfig.TLSCAFile, "path to TLS certificate Authority PEM file for remote, (CONTAINER_TLS_CA)")
_ = cmd.RegisterFlagCompletionFunc(tlsCAFileFlagName, completion.AutocompleteDefault)

// Flags that control or influence any kind of output.
outFlagName := "out"
lFlags.StringVar(&useStdout, outFlagName, "", "Send output (stdout) from podman to a file")
Expand Down
50 changes: 41 additions & 9 deletions cmd/podman/system/connection/add.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ var (
"destination" is one of the form:
[user@]hostname (will default to ssh)
ssh://[user@]hostname[:port][/path] (will obtain socket path from service, if not given.)
tcp://hostname:port (not secured)
tcp://hostname:port (not secured without TLS enabled)
unix://path (absolute path required)
`,
RunE: add,
Expand All @@ -36,6 +36,7 @@ var (
podman system connection add --identity ~/.ssh/dev_rsa testing ssh://[email protected]:2222
podman system connection add --identity ~/.ssh/dev_rsa --port 22 production [email protected]
podman system connection add debug tcp://localhost:8080
podman system connection add production-tls --tls-ca=ca.crt --tls-cert=tls.crt --tls-key=tls.key tcp://localhost:8080
`,
}

Expand All @@ -51,11 +52,14 @@ var (
dockerPath string

cOpts = struct {
Identity string
Port int
UDSPath string
Default bool
Farm string
Identity string
Port int
UDSPath string
Default bool
Farm string
TLSCertFile string
TLSKeyFile string
TLSCAFile string
}{}
)

Expand All @@ -74,6 +78,18 @@ func init() {
flags.StringVar(&cOpts.Identity, identityFlagName, "", "path to SSH identity file")
_ = addCmd.RegisterFlagCompletionFunc(identityFlagName, completion.AutocompleteDefault)

tlsCertFileFlagName := "tls-cert"
flags.StringVar(&cOpts.TLSCertFile, tlsCertFileFlagName, "", "path to TLS client certificate PEM file")
_ = addCmd.RegisterFlagCompletionFunc(tlsCertFileFlagName, completion.AutocompleteDefault)

tlsKeyFileFlagName := "tls-key"
flags.StringVar(&cOpts.TLSKeyFile, tlsKeyFileFlagName, "", "path to TLS client certificate private key PEM file")
_ = addCmd.RegisterFlagCompletionFunc(tlsKeyFileFlagName, completion.AutocompleteDefault)

tlsCAFileFlagName := "tls-ca"
flags.StringVar(&cOpts.TLSCAFile, tlsCAFileFlagName, "", "path to TLS certificate Authority PEM file")
_ = addCmd.RegisterFlagCompletionFunc(tlsCAFileFlagName, completion.AutocompleteDefault)

socketPathFlagName := "socket-path"
flags.StringVar(&cOpts.UDSPath, socketPathFlagName, "", "path to podman socket on remote host. (default '/run/podman/podman.sock' or '/run/user/{uid}/podman/podman.sock)")
_ = addCmd.RegisterFlagCompletionFunc(socketPathFlagName, completion.AutocompleteDefault)
Expand Down Expand Up @@ -139,14 +155,24 @@ func add(cmd *cobra.Command, args []string) error {
return fmt.Errorf("invalid ssh mode")
}

if uri.Scheme != "tcp" {
if cmd.Flags().Changed("tls-cert") {
return fmt.Errorf("--tls-cert option not supported for %s scheme", uri.Scheme)
}
if cmd.Flags().Changed("tls-key") {
return fmt.Errorf("--tls-key option not supported for %s scheme", uri.Scheme)
}
if cmd.Flags().Changed("tls-ca") {
return fmt.Errorf("--tls-ca option not supported for %s scheme", uri.Scheme)
}
}
switch uri.Scheme {
case "ssh":
return ssh.Create(entities, sshMode)
case "unix":
if cmd.Flags().Changed("identity") {
return errors.New("--identity option not supported for unix scheme")
}

if cmd.Flags().Changed("socket-path") {
uri.Path = cmd.Flag("socket-path").Value.String()
}
Expand All @@ -169,6 +195,9 @@ func add(cmd *cobra.Command, args []string) error {
if cmd.Flags().Changed("identity") {
return errors.New("--identity option not supported for tcp scheme")
}
if cmd.Flags().Changed("tls-cert") != cmd.Flags().Changed("tls-key") {
return errors.New("--tls-cert and --tls-key options must be both provided if one is provided")
}
if uri.Port() == "" {
return errors.New("tcp scheme requires a port either via --port or in destination URL")
}
Expand All @@ -177,8 +206,11 @@ func add(cmd *cobra.Command, args []string) error {
}

dst := config.Destination{
URI: uri.String(),
Identity: cOpts.Identity,
URI: uri.String(),
Identity: cOpts.Identity,
TLSCertFile: cOpts.TLSCertFile,
TLSKeyFile: cOpts.TLSKeyFile,
TLSCAFile: cOpts.TLSCAFile,
}

connection := args[0]
Expand Down
15 changes: 9 additions & 6 deletions cmd/podman/system/connection/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,19 +118,22 @@ func inspect(cmd *cobra.Command, args []string) error {
rpt, err = rpt.Parse(report.OriginUser, format)
} else {
rpt, err = rpt.Parse(report.OriginPodman,
"{{range .}}{{.Name}}\t{{.URI}}\t{{.Identity}}\t{{.Default}}\t{{.ReadWrite}}\n{{end -}}")
"{{range .}}{{.Name}}\t{{.URI}}\t{{.Identity}}\t{{.TLSCAFile}}\t{{.TLSCertFile}}\t{{.TLSKeyFile}}\t{{.Default}}\t{{.ReadWrite}}\n{{end -}}")
Copy link
Member

Choose a reason for hiding this comment

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

IMO this is a breaking change. While nobody should relay on the order of the output and use --format if they use it in scripts we can never know.
I guess I could be convinced to add them as last keys after readwrite.

Copy link
Author

Choose a reason for hiding this comment

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

I would agree, and while I'm not sure if podman or the the umbrella containers project has strict guidelines on the topic, I've always considered "human readable" data to be excluded from breaking changes. I would no sooner worry about breaking scripts that scrape this output than scripts that break if a new log message was added.

}
if err != nil {
return err
}

if rpt.RenderHeaders {
err = rpt.Execute([]map[string]string{{
"Default": "Default",
"Identity": "Identity",
"Name": "Name",
"URI": "URI",
"ReadWrite": "ReadWrite",
"Default": "Default",
"Identity": "Identity",
"TLSCAFile": "TLSCAFile",
"TLSCertFile": "TLSCertFile",
"TLSKeyFile": "TLSKeyFile",
"Name": "Name",
"URI": "URI",
"ReadWrite": "ReadWrite",
}})
if err != nil {
return err
Expand Down
35 changes: 27 additions & 8 deletions cmd/podman/system/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,19 @@ Enable a listening service for API access to Podman commands.
RunE: service,
ValidArgsFunction: common.AutocompleteDefaultOneArg,
Example: `podman system service --time=0 unix:///tmp/podman.sock
podman system service --time=0 tcp://localhost:8888`,
podman system service --time=0 tcp://localhost:8888
podman system service --time=0 --tls-cert=tls.crt --tls-key=tls.key tcp://localhost:8888
podman system service --time=0 --tls-cert=tls.crt --tls-key=tls.key --tls-client-ca=ca.crt tcp://localhost:8888
`,
}

srvArgs = struct {
CorsHeaders string
PProfAddr string
Timeout uint
CorsHeaders string
PProfAddr string
Timeout uint
TLSCertFile string
TLSKeyFile string
TLSClientCAFile string
}{}
)

Expand All @@ -67,6 +73,16 @@ func init() {
flags.StringVarP(&srvArgs.PProfAddr, "pprof-address", "", "",
"Binding network address for pprof profile endpoints, default: do not expose endpoints")
_ = flags.MarkHidden("pprof-address")

flags.StringVarP(&srvArgs.TLSCertFile, "tls-cert", "", "",
"PEM file containing TLS serving certificate.")
_ = srvCmd.RegisterFlagCompletionFunc("tls-cert", completion.AutocompleteDefault)
flags.StringVarP(&srvArgs.TLSKeyFile, "tls-key", "", "",
"PEM file containing TLS serving certificate private key")
_ = srvCmd.RegisterFlagCompletionFunc("tls-key", completion.AutocompleteDefault)
flags.StringVarP(&srvArgs.TLSClientCAFile, "tls-client-ca", "", "",
"Only trust client connections with certificates signed by this CA PEM file")
_ = srvCmd.RegisterFlagCompletionFunc("tls-client-ca", completion.AutocompleteDefault)
}

func aliasTimeoutFlag(_ *pflag.FlagSet, name string) pflag.NormalizedName {
Expand Down Expand Up @@ -100,10 +116,13 @@ func service(cmd *cobra.Command, args []string) error {
}

return restService(cmd.Flags(), registry.PodmanConfig(), entities.ServiceOptions{
CorsHeaders: srvArgs.CorsHeaders,
PProfAddr: srvArgs.PProfAddr,
Timeout: time.Duration(srvArgs.Timeout) * time.Second,
URI: apiURI,
CorsHeaders: srvArgs.CorsHeaders,
PProfAddr: srvArgs.PProfAddr,
Timeout: time.Duration(srvArgs.Timeout) * time.Second,
URI: apiURI,
TLSCertFile: srvArgs.TLSCertFile,
TLSKeyFile: srvArgs.TLSKeyFile,
TLSClientCAFile: srvArgs.TLSClientCAFile,
})
}

Expand Down
6 changes: 4 additions & 2 deletions cmd/podman/system/service_abi.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,11 +76,13 @@ func restService(flags *pflag.FlagSet, cfg *entities.PodmanConfig, opts entities
}
}
case "tcp":
// We want to check if the user is requesting a TCP address.
// We want to check if the user is requesting a TCP address if TLS is not active.
// If so, warn that this is insecure.
// Ignore errors here, the actual backend code will handle them
// better than we can here.
logrus.Warnf("Using the Podman API service with TCP sockets is not recommended, please see `podman system service` manpage for details")
if opts.TLSKeyFile == "" || opts.TLSCertFile == "" {
logrus.Warnf("Using the Podman API service with TCP sockets without TLS is not recommended, please see `podman system service` manpage for details")
}

host := uri.Host
if host == "" {
Expand Down
16 changes: 16 additions & 0 deletions docs/source/markdown/podman-system-connection-add.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,18 @@ Path to ssh identity file. If the identity file has been encrypted, Podman promp
If no identity file is provided and no user is given, Podman defaults to the user running the podman command.
Podman prompts for the login password on the remote server.

#### --tls-cert=path

Path to a PEM file containing the TLS client certificate to present to the server. `--tls-key` must also be provided.

#### --tls-key=path

Path to a PEM file containing the private key matching `--tls-cert`. `--tls-cert` must also be provided.

#### --tls-ca=path

Path to a PEM file containing the certificate authority bundle to verify the server's certificate against.

#### **--port**, **-p**=*port*

Port for ssh destination. The default value is `22`.
Expand Down Expand Up @@ -56,6 +68,10 @@ Add a named system connection to local tcp socket:
```
$ podman system connection add debug tcp://localhost:8080
```
Add a named system connection to remote tcp socket secured via TLS:
```
$ podman system connection add secure-debug --tls-cert=tls.crt --tls-key=tls.key --tls-ca=ca.crt tcp://podman.example.com:8443
```
## SEE ALSO
**[podman(1)](podman.1.md)**, **[podman-system(1)](podman-system.1.md)**, **[podman-system-connection(1)](podman-system-connection.1.md)**

Expand Down
18 changes: 16 additions & 2 deletions docs/source/markdown/podman-system-service.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,17 +70,31 @@ To access the API service inside a container:

Please note that the API grants full access to all Podman functionality, and thus allows arbitrary code execution as the user running the API, with no ability to limit or audit this access.
The API's security model is built upon access via a Unix socket with access restricted via standard file permissions, ensuring that only the user running the service will be able to access it.
We *strongly* recommend against making the API socket available via the network (IE, bindings the service to a *tcp* URL).
TLS can be used to secure this socket by requiring clients to present a certificate signed by a trusted certificate authority ("CA"), as well as to allow the client to verify the identity of the API.
We *strongly* recommend against making the API socket available via the network (IE, bindings the service to a *tcp* URL) without enabling mutual TLS to authenticate the client.
Even access via Localhost carries risks - anyone with access to the system will be able to access the API.
If remote access is required, we instead recommend forwarding the API socket via SSH, and limiting access on the remote machine to the greatest extent possible.
If a *tcp* URL must be used, using the *--cors* option is recommended to improve security.
If a *tcp* URL must be used without TLS, using the *--cors* option is recommended to improve security.

## OPTIONS

#### **--cors**

CORS headers to inject to the HTTP response. The default value is empty string which disables CORS headers.

#### --tls-cert=path

Path to a PEM file containing the TLS certificate to present to clients. `--tls-key` must also be provided.

#### --tls-key=path

Path to a PEM file containing the private key matching `--tls-cert`. `--tls-cert` must also be provided.

#### --tls-client-ca=path

Path to a PEM file containing the TLS certificate bundle to validate client connections against.
Connections that present no certificate or a certificate not signed by one of these certificates will be rejected.

#### **--help**, **-h**

Print usage statement.
Expand Down
2 changes: 1 addition & 1 deletion internal/domain/infra/runtime_abi.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ func NewTestingEngine(facts *entities.PodmanConfig) (ientities.TestingEngine, er
r, err := NewLibpodTestingRuntime(facts.FlagSet, facts)
return r, err
case entities.TunnelMode:
ctx, err := bindings.NewConnectionWithIdentity(context.Background(), facts.URI, facts.Identity, facts.MachineMode)
ctx, err := bindings.NewConnectionWithIdentityOrTLS(context.Background(), facts.URI, facts.Identity, facts.TLSCertFile, facts.TLSKeyFile, facts.TLSCAFile, facts.MachineMode)
return &tunnel.TestingEngine{ClientCtx: ctx}, err
}
return nil, fmt.Errorf("runtime mode '%v' is not supported", facts.EngineMode)
Expand Down
Loading
Loading