Skip to content

Commit cf886b4

Browse files
committed
Add c/image/pkg/cli/tlsopts/tlscobra
Signed-off-by: Miloslav Trmač <mitr@redhat.com>
1 parent bb6cedf commit cf886b4

3 files changed

Lines changed: 164 additions & 0 deletions

File tree

image/pkg/cli/tlsopts/tls.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
// Package tlsopts provides a common implementation of parsing TLS configuration options,
22
// to ensure consistent semantics across containers/* projects.
3+
//
4+
// Consider using c/image/pkg/cli/tlsopts/tlscobra instead.
35
package tlsopts
46

57
import (
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
// Package tlscobra implements Cobra CLI flags for tlsopts.
2+
//
3+
// Recommended flag documentation:
4+
//
5+
// --tls-min-version sets the minimum TLS version to use throughout the program.
6+
// If not set, defaults to a reasonable default that may change over time (depending on system’s global policy,
7+
// version of the program, version of the Go language, and the like).
8+
// Users should generally not use this option and hard-code a version unless they have a process
9+
// to ensure that the value will be kept up to date.
10+
//
11+
//
12+
// --tls-cipher-suites sets the allowed TLS cipher suites to use throughout the program.
13+
// The value is a comma-separated list of IANA TLS Cipher Suites names.
14+
//
15+
// If not set, defaults to a reasonable default that may change over time (depending on system’s global policy,
16+
// version of the program, version of the Go language, and the like).
17+
//
18+
// Warning: Almost no-one should ever use this option. Use it only if you have a bureaucracy that requires a specific list,
19+
// and if you are confident that this bureaucracy will still exist, and will bring you an updated list when necessary,
20+
// many years from now.
21+
//
22+
// Warning: The effectiveness of this option is limited by capabilities of the Go standard library;
23+
// e.g., as of Go 1.25, it is not possible to change which cipher suites are used in TLS 1.3.
24+
//
25+
//
26+
// --tls-named-groups sets the allowed TLS named groups to use throughout the program.
27+
// The value is a comma-separated list of IANA TLS Supported Groups names.
28+
//
29+
// If not set, defaults to a reasonable default that may change over time (depending on system’s global policy,
30+
// version of the program, version of the Go language, and the like).
31+
//
32+
// Warning: Almost no-one should ever use this option. Use it only if you have a bureaucracy that requires a specific list,
33+
// and if you are confident that this bureaucracy will still exist, and will bring you an updated list when necessary,
34+
// many years from now.
35+
package tlscobra
36+
37+
import (
38+
"crypto/tls"
39+
"errors"
40+
41+
"github.com/spf13/pflag"
42+
"go.podman.io/image/v5/pkg/cli/tlsopts"
43+
)
44+
45+
// TLSOptions is a set of TLS-related CLI options.
46+
type TLSOptions struct {
47+
minVersion onceStringValue
48+
cipherSuites onceStringValue
49+
namedGroups onceStringValue
50+
}
51+
52+
// TLSFlags returns a collection of CLI flags writing into TLSOptions, and the managed TLSOptions structure.
53+
func TLSFlags() (pflag.FlagSet, *TLSOptions) {
54+
opts := TLSOptions{}
55+
fs := pflag.FlagSet{}
56+
fs.Var(&opts.minVersion, "tls-min-version", "Minimum TLS version")
57+
fs.Var(&opts.cipherSuites, "tls-cipher-suites", "Comma-separated list of TLS cipher suites")
58+
fs.Var(&opts.namedGroups, "tls-named-groups", "Comma-separated list of TLS named groups")
59+
60+
return fs, &opts
61+
}
62+
63+
// BaseTLSConfig returns a *tls.Config configured according to opts.
64+
// If opts contains no settings (all options are empty or unset), it returns (nil, nil).
65+
// Otherwise, the returned *tls.Config is freshly allocated and the caller can modify it as needed.
66+
func (opts *TLSOptions) BaseTLSConfig() (*tls.Config, error) {
67+
// We don’t need to inspect .present, .value defaults to "".
68+
return tlsopts.BaseTLSConfig(
69+
tlsopts.WithMinVersion(opts.minVersion.value),
70+
tlsopts.WithCipherSuites(opts.cipherSuites.value),
71+
tlsopts.WithNamedGroups(opts.namedGroups.value),
72+
)
73+
}
74+
75+
// onceStringValue is a flag.Value implementation equivalent to the one underlying pflag.String,
76+
// except that it fails when set more than once (even to the same value).
77+
// Compare https://github.com/spf13/pflag/issues/72 .
78+
type onceStringValue struct {
79+
present bool
80+
value string // Valid even if !present, defaults to "".
81+
}
82+
83+
func (os *onceStringValue) String() string {
84+
return os.value
85+
}
86+
87+
func (os *onceStringValue) Set(val string) error {
88+
if os.present {
89+
return errors.New("flag already set")
90+
}
91+
os.present = true
92+
os.value = val
93+
return nil
94+
}
95+
96+
func (os *onceStringValue) Type() string {
97+
return "string"
98+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package tlscobra
2+
3+
import (
4+
"crypto/tls"
5+
"io"
6+
"testing"
7+
8+
"github.com/spf13/cobra"
9+
"github.com/stretchr/testify/assert"
10+
"github.com/stretchr/testify/require"
11+
)
12+
13+
func TestTLSFlags(t *testing.T) {
14+
for _, tc := range []struct {
15+
args []string
16+
expected *tls.Config
17+
expectError bool
18+
}{
19+
{[]string{}, nil, false}, // no options
20+
// Each option works
21+
{[]string{"--tls-min-version", "1.2"}, &tls.Config{MinVersion: tls.VersionTLS12}, false},
22+
{[]string{"--tls-cipher-suites", "TLS_AES_128_GCM_SHA256"}, &tls.Config{CipherSuites: []uint16{tls.TLS_AES_128_GCM_SHA256}}, false},
23+
{[]string{"--tls-named-groups", "secp256r1"}, &tls.Config{CurvePreferences: []tls.CurveID{tls.CurveP256}}, false},
24+
// A smoke-test of all options together
25+
{
26+
[]string{"--tls-min-version", "1.2", "--tls-cipher-suites", "TLS_AES_128_GCM_SHA256", "--tls-named-groups", "secp256r1"},
27+
&tls.Config{MinVersion: tls.VersionTLS12, CipherSuites: []uint16{tls.TLS_AES_128_GCM_SHA256}, CurvePreferences: []tls.CurveID{tls.CurveP256}},
28+
false,
29+
},
30+
// Duplicate options are rejected.
31+
{[]string{"--tls-min-version", "1.2", "--tls-min-version", "1.2"}, nil, true},
32+
{[]string{"--tls-cipher-suites", "TLS_AES_128_GCM_SHA256", "--tls-cipher-suites", "TLS_AES_128_GCM_SHA256"}, nil, true},
33+
{[]string{"--tls-named-groups", "secp256r1", "--tls-named-groups", "secp256r1"}, nil, true},
34+
// A smoke-test of error handling; this is tested in detail in tlsopts.
35+
{[]string{"--tls-min-version", "this_is_invalid"}, nil, true},
36+
} {
37+
fs, opts := TLSFlags()
38+
actionRun := false
39+
var useError error
40+
app := cobra.Command{
41+
RunE: func(_ *cobra.Command, args []string) error {
42+
config, err := opts.BaseTLSConfig()
43+
useError = err
44+
if err == nil {
45+
assert.Equal(t, tc.expected, config, tc.args)
46+
}
47+
actionRun = true
48+
return nil
49+
},
50+
}
51+
app.Flags().AddFlagSet(&fs)
52+
app.SetOut(io.Discard)
53+
app.SetErr(io.Discard)
54+
app.SetArgs(tc.args)
55+
topErr := app.Execute()
56+
if !tc.expectError {
57+
assert.True(t, actionRun)
58+
require.NoError(t, topErr, tc.args)
59+
require.NoError(t, useError, tc.args)
60+
} else {
61+
assert.True(t, topErr != nil || useError != nil)
62+
}
63+
}
64+
}

0 commit comments

Comments
 (0)