@@ -17,11 +17,6 @@ import (
17
17
"github.com/lestrrat-go/jwx/v2/jws"
18
18
)
19
19
20
- // EnvNamespacePrefix is the string that prefixes all fields in the "env"
21
- // namespace. This is used to separate signed data that came from the
22
- // environment from data that came from an object.
23
- const EnvNamespacePrefix = "env::"
24
-
25
20
// SignedFielder describes types that can be signed and have signatures
26
21
// verified.
27
22
// Converting non-string fields into strings (in a stable, canonical way) is an
@@ -43,31 +38,51 @@ type SignedFielder interface {
43
38
type Logger interface { Debug (f string , v ... any ) }
44
39
45
40
type options struct {
46
- env map [string ]string
41
+ env map [string ]string // not used in Sign or Verify
47
42
logger Logger
48
43
debugSigning bool
49
44
}
50
45
46
+ // Allow *options to pass through SignOrVerifyOption.
47
+ func (o * options ) apply (opts * options ) { * opts = * o }
48
+ func (* options ) signOrVerifyTag () {}
49
+
50
+ // Option implementations provide extra parameters. This type encompasses all
51
+ // options, whether or not they are allowed to be passed to Sign or Verify.
51
52
type Option interface {
52
53
apply (* options )
53
54
}
54
55
55
- type envOption struct { env map [string ]string }
56
+ // SignOrVerifyOption are the subtype of options that can be passed to Sign
57
+ // or to Verify.
58
+ type SignOrVerifyOption interface {
59
+ Option
60
+
61
+ // This tag ensures that options that aren't one of the specifically-tagged
62
+ // options cannot be passed to Sign or Verify.
63
+ signOrVerifyTag ()
64
+ }
65
+
56
66
type loggerOption struct { logger Logger }
67
+
68
+ func (o loggerOption ) apply (opts * options ) { opts .logger = o .logger }
69
+ func (loggerOption ) signOrVerifyTag () {}
70
+
57
71
type debugSigningOption struct { debugSigning bool }
58
72
59
- func (o envOption ) apply (opts * options ) { opts .env = o .env }
60
- func (o loggerOption ) apply (opts * options ) { opts .logger = o .logger }
61
73
func (o debugSigningOption ) apply (opts * options ) { opts .debugSigning = o .debugSigning }
74
+ func (debugSigningOption ) signOrVerifyTag () {}
62
75
63
- func WithEnv (env map [string ]string ) Option { return envOption {env } }
64
- func WithLogger (logger Logger ) Option { return loggerOption {logger } }
65
- func WithDebugSigning (debugSigning bool ) Option { return debugSigningOption {debugSigning } }
76
+ // WithLogger provides a logger to use for debug logging.
77
+ func WithLogger (logger Logger ) SignOrVerifyOption { return loggerOption {logger } }
66
78
67
- func configureOptions (opts ... Option ) options {
68
- options := options {
69
- env : make (map [string ]string ),
70
- }
79
+ // WithDebugSigning enables or disables signing debugging. Aside from logging
80
+ // verbosely, enabling this may risk disclosing information that could break the
81
+ // encryption properties of the signature.
82
+ func WithDebugSigning (debugSigning bool ) SignOrVerifyOption { return debugSigningOption {debugSigning } }
83
+
84
+ func configureOptions [E Option ](opts []E ) options {
85
+ options := options {}
71
86
for _ , o := range opts {
72
87
o .apply (& options )
73
88
}
@@ -78,11 +93,11 @@ type Key interface {
78
93
Algorithm () jwa.KeyAlgorithm
79
94
}
80
95
81
- // Sign computes a new signature for an environment (env) combined with an
82
- // object containing values (sf) using a given key. The key can be a jwk.Key
83
- // or a crypto.Signer. If it is a jwk.Key, the public key thumbprint is logged.
84
- func Sign (_ context.Context , key Key , sf SignedFielder , opts ... Option ) (* pipeline.Signature , error ) {
85
- options := configureOptions (opts ... )
96
+ // Sign computes a new signature for object containing values (sf) using a given
97
+ // key. The key can be a jwk.Key or a crypto.Signer. If it is a jwk.Key, the
98
+ // public key thumbprint is logged.
99
+ func Sign (_ context.Context , key Key , sf SignedFielder , opts ... SignOrVerifyOption ) (* pipeline.Signature , error ) {
100
+ options := configureOptions (opts )
86
101
87
102
values , err := sf .SignedFields ()
88
103
if err != nil {
@@ -92,21 +107,6 @@ func Sign(_ context.Context, key Key, sf SignedFielder, opts ...Option) (*pipeli
92
107
return nil , errors .New ("no fields to sign" )
93
108
}
94
109
95
- // Step env overrides pipeline and build env:
96
- // https://buildkite.com/docs/tutorials/pipeline-upgrade#what-is-the-yaml-steps-editor-compatibility-issues
97
- // (Beware of inconsistent docs written in the time of legacy steps.)
98
- // So if the thing we're signing has an env map, use it to exclude pipeline
99
- // vars from signing.
100
- objEnv , _ := values ["env" ].(map [string ]string )
101
-
102
- // Namespace the env values and include them in the values to sign.
103
- for k , v := range options .env {
104
- if _ , has := objEnv [k ]; has {
105
- continue
106
- }
107
- values [EnvNamespacePrefix + k ] = v
108
- }
109
-
110
110
// Extract the names of the fields.
111
111
fields := make ([]string , 0 , len (values ))
112
112
for field := range values {
@@ -166,8 +166,8 @@ func Sign(_ context.Context, key Key, sf SignedFielder, opts ...Option) (*pipeli
166
166
// Verify verifies an existing signature against environment (env) combined with
167
167
// the keyset. The keySet can be a jwk.Set or a crypto.Signer. If it is a jwk.Set,
168
168
// the public key thumbprints are logged.
169
- func Verify (ctx context.Context , s * pipeline.Signature , keySet any , sf SignedFielder , opts ... Option ) error {
170
- options := configureOptions (opts ... )
169
+ func Verify (ctx context.Context , s * pipeline.Signature , keySet any , sf SignedFielder , opts ... SignOrVerifyOption ) error {
170
+ options := configureOptions (opts )
171
171
172
172
if len (s .SignedFields ) == 0 {
173
173
return errors .New ("signature covers no fields" )
@@ -179,23 +179,6 @@ func Verify(ctx context.Context, s *pipeline.Signature, keySet any, sf SignedFie
179
179
return fmt .Errorf ("obtaining values for fields: %w" , err )
180
180
}
181
181
182
- // See Sign above for why we need special handling for step env.
183
- objEnv , _ := values ["env" ].(map [string ]string )
184
-
185
- // Namespace the env values and include them in the values to sign.
186
- for k , v := range options .env {
187
- if _ , has := objEnv [k ]; has {
188
- continue
189
- }
190
- values [EnvNamespacePrefix + k ] = v
191
- }
192
-
193
- // env:: fields that were signed are all required from the env map.
194
- // We can't verify other env vars though - they can vary for lots of reasons
195
- // (e.g. Buildkite-provided vars added by the backend.)
196
- // This is still strong enough for a user to enforce any particular env var
197
- // exists and has a particular value - make it a part of the pipeline or
198
- // step env.
199
182
required , err := requireKeys (values , s .SignedFields )
200
183
if err != nil {
201
184
return fmt .Errorf ("obtaining required keys: %w" , err )
0 commit comments