Skip to content

Commit

Permalink
Add flags to "system service" and "system connnection add" for TLS ma…
Browse files Browse the repository at this point in the history
…terial for TCP remotes

Signed-off-by: Andrew Melnick <meln5674@kettering.edu>
  • Loading branch information
meln5674 committed Nov 18, 2024
1 parent ec691c7 commit cceab7f
Show file tree
Hide file tree
Showing 12 changed files with 264 additions and 44 deletions.
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, "", "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, "", "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, "", "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
56 changes: 48 additions & 8 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://root@server.fubar.com:2222
podman system connection add --identity ~/.ssh/dev_rsa --port 22 production root@server.fubar.com
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 @@ -141,11 +157,29 @@ func add(cmd *cobra.Command, args []string) error {

switch uri.Scheme {
case "ssh":
if cmd.Flags().Changed("tls-cert") {
return errors.New("--tls-cert option not supported for ssh scheme")
}
if cmd.Flags().Changed("tls-key") {
return errors.New("--tls-key option not supported for ssh scheme")
}
if cmd.Flags().Changed("tls-ca") {
return errors.New("--tls-ca option not supported for ssh scheme")
}
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("tls-cert") {
return errors.New("--tls-cert option not supported for unix scheme")
}
if cmd.Flags().Changed("tls-key") {
return errors.New("--tls-key option not supported for unix scheme")
}
if cmd.Flags().Changed("tls-ca") {
return errors.New("--tls-ca option not supported for unix scheme")
}

if cmd.Flags().Changed("socket-path") {
uri.Path = cmd.Flag("socket-path").Value.String()
Expand All @@ -169,6 +203,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 +214,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
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
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
38 changes: 33 additions & 5 deletions pkg/api/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package server

import (
"context"
"crypto/tls"
"fmt"
"log"
"net"
Expand All @@ -22,6 +23,7 @@ import (
"github.com/containers/podman/v5/pkg/api/server/idle"
"github.com/containers/podman/v5/pkg/api/types"
"github.com/containers/podman/v5/pkg/domain/entities"
"github.com/containers/podman/v5/pkg/util"
"github.com/coreos/go-systemd/v22/daemon"
"github.com/gorilla/mux"
"github.com/gorilla/schema"
Expand All @@ -38,6 +40,9 @@ type APIServer struct {
CorsHeaders string // Inject Cross-Origin Resource Sharing (CORS) headers
PProfAddr string // Binding network address for pprof profiles
idleTracker *idle.Tracker // Track connections to support idle shutdown
tlsCertFile string // TLS serving certificate PEM file
tlsKeyFile string // TLS serving certificate private key PEM file
tlsClientCAFile string // TLS client certifiicate CA bundle PEM file
}

// Number of seconds to wait for next request, if exceeded shutdown server
Expand Down Expand Up @@ -84,10 +89,13 @@ func newServer(runtime *libpod.Runtime, listener net.Listener, opts entities.Ser
Handler: router,
IdleTimeout: opts.Timeout * 2,
},
CorsHeaders: opts.CorsHeaders,
Listener: listener,
PProfAddr: opts.PProfAddr,
idleTracker: tracker,
CorsHeaders: opts.CorsHeaders,
Listener: listener,
PProfAddr: opts.PProfAddr,
idleTracker: tracker,
tlsCertFile: opts.TLSCertFile,
tlsKeyFile: opts.TLSKeyFile,
tlsClientCAFile: opts.TLSClientCAFile,
}

server.BaseContext = func(l net.Listener) context.Context {
Expand All @@ -98,6 +106,18 @@ func newServer(runtime *libpod.Runtime, listener net.Listener, opts entities.Ser
return ctx
}

if opts.TLSClientCAFile != "" {
logrus.Debugf("will validate client certs against %s", opts.TLSClientCAFile)
pool, err := util.ReadCertBundle(opts.TLSClientCAFile)
if err != nil {
return nil, err
}
server.TLSConfig = &tls.Config{
ClientCAs: pool,
ClientAuth: tls.RequireAndVerifyClientCert,
}
}

// Capture panics and print stack traces for diagnostics,
// additionally process X-Reference-Id Header to support event correlation
router.Use(panicHandler(), referenceIDHandler())
Expand Down Expand Up @@ -224,7 +244,15 @@ func (s *APIServer) Serve() error {
errChan := make(chan error, 1)
s.setupSystemd()
go func() {
err := s.Server.Serve(s.Listener)
var err error
if s.tlsClientCAFile != "" || (s.tlsCertFile != "" && s.tlsKeyFile != "") {
if s.tlsCertFile != "" && s.tlsKeyFile != "" {
logrus.Debugf("serving TLS with cert %s and key %s", s.tlsCertFile, s.tlsKeyFile)
}
err = s.Server.ServeTLS(s.Listener, s.tlsCertFile, s.tlsKeyFile)
} else {
err = s.Server.Serve(s.Listener)
}
if err != nil && err != http.ErrServerClosed {
errChan <- fmt.Errorf("failed to start API service: %w", err)
return
Expand Down
Loading

0 comments on commit cceab7f

Please sign in to comment.