Skip to content
This repository was archived by the owner on Oct 31, 2023. It is now read-only.

Commit b86987e

Browse files
authored
Merge pull request #2 from mitchellh/etter-tanium-standalone-notarization
Support a notarization-only mode, thanks to @etter-tanium
2 parents 419730c + 3cd4dbc commit b86987e

File tree

9 files changed

+287
-72
lines changed

9 files changed

+287
-72
lines changed

README.md

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,21 @@ Supported configurations:
190190
for your application. You should choose something unique for your application.
191191
You can also [register these with Apple](https://developer.apple.com/account/resources/identifiers/list).
192192

193+
* `notarize` (_optional_) - Settings for notarizing an already built files.
194+
This is an alternative to using the `source` option.
195+
196+
* `path` (`string`) - The path to the file to notarize. This must be
197+
one of Apple's supported file types for notarization: dmg, pkg, app, or
198+
zip.
199+
200+
* `bundle_id` (`string`) - The bundle ID to use for this notarization.
201+
This is used instead of the top-level `bundle_id` (which controls the
202+
value for source-based runs).
203+
204+
* `staple` (`bool` _optional_) - Controls if `stapler staple` should run
205+
if notarization succeeds. This should only be set for filetypes that
206+
support it (dmg, pkg, or app).
207+
193208
* `apple_id` - Settings related to the Apple ID to use for notarization.
194209

195210
* `username` (`string`) - The Apple ID username, typically an email address.
@@ -232,6 +247,55 @@ Supported configurations:
232247
already exists, it will be overwritten. All files in `source` will be copied
233248
into the root of the zip archive.
234249

250+
### Notarization-Only Configuration
251+
252+
You can configure `gon` to notarize already-signed files. This is useful
253+
if you're integrating `gon` into an existing build pipeline that may already
254+
support creation of pkg, app, etc. files.
255+
256+
You can use this in addition to specifying `source` as well. In this case,
257+
we will codesign & package the files specified in `source` and then notarize
258+
those results as well as those in `notarize` blocks.
259+
260+
Example in HCL and then the identical configuration in JSON:
261+
262+
```hcl
263+
sources = []
264+
bundle_id = ""
265+
266+
notarize {
267+
path = "/path/to/terraform.pkg"
268+
bundle_id = "com.mitchellh.example.terraform"
269+
staple = true
270+
}
271+
272+
apple_id {
273+
username = "mitchell@example.com"
274+
password = "@env:AC_PASSWORD"
275+
}
276+
```
277+
278+
```json
279+
{
280+
"sources": [],
281+
"bundle_id": "",
282+
283+
"notarize": [{
284+
"path": "/path/to/terraform.pkg",
285+
"bundle_id": "com.mitchellh.example.terraform",
286+
"staple": true
287+
}],
288+
289+
"apple_id": {
290+
"username": "mitchell@example.com",
291+
"password": "@env:AC_PASSWORD"
292+
}
293+
}
294+
```
295+
296+
Note you may specify multiple `notarize` blocks to notarize multipel files
297+
concurrently.
298+
235299
### Processing Time
236300

237301
The notarization process requires submitting your package(s) to Apple

cmd/gon/item.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ type item struct {
1818
// Path is the path to the file to notarize.
1919
Path string
2020

21+
// BundleId is the bundle ID to use for this notarization.
22+
BundleId string
23+
2124
// Staple is true if we should perform stapling on this file. Not
2225
// all files support stapling so the default depends on the type of file.
2326
Staple bool
@@ -54,10 +57,16 @@ type processOptions struct {
5457
func (i *item) notarize(ctx context.Context, opts *processOptions) error {
5558
lock := opts.OutputLock
5659

60+
// The bundle ID defaults to the root one
61+
bundleId := i.BundleId
62+
if bundleId == "" {
63+
bundleId = opts.Config.BundleId
64+
}
65+
5766
// Start notarization
5867
_, err := notarize.Notarize(ctx, &notarize.Options{
5968
File: i.Path,
60-
BundleId: opts.Config.BundleId,
69+
BundleId: bundleId,
6170
Username: opts.Config.AppleId.Username,
6271
Password: opts.Config.AppleId.Password,
6372
Provider: opts.Config.AppleId.Provider,

cmd/gon/main.go

Lines changed: 116 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -75,101 +75,149 @@ func realMain() int {
7575
return 1
7676
}
7777

78-
// If we have no items to sign then its probably an error
79-
if len(cfg.Source) == 0 {
80-
color.New(color.Bold, color.FgRed).Fprintf(os.Stdout, "❗️ No source files specified\n")
81-
color.New(color.FgRed).Fprintf(os.Stdout,
82-
"Your configuration had an empty 'source' value. This must be populated with\n"+
83-
"at least one file to sign, package, and notarize.\n")
84-
return 1
85-
}
86-
8778
// The files to notarize should be added to this. We'll submit one notarization
8879
// request per file here.
8980
var items []*item
9081

91-
// Perform codesigning
92-
color.New(color.Bold).Fprintf(os.Stdout, "==> %s Signing files...\n", iconSign)
93-
err = sign.Sign(context.Background(), &sign.Options{
94-
Files: cfg.Source,
95-
Identity: cfg.Sign.ApplicationIdentity,
96-
Logger: logger.Named("sign"),
97-
})
98-
if err != nil {
99-
fmt.Fprintf(os.Stdout, color.RedString("❗️ Error signing files:\n\n%s\n", err))
100-
return 1
82+
// Notarize is an alternative to "Source", where you specify
83+
// a single .pkg or .zip that is ready for notarization and stapling
84+
if len(cfg.Notarize) > 0 {
85+
for _, c := range cfg.Notarize {
86+
items = append(items, &item{
87+
Path: c.Path,
88+
BundleId: c.BundleId,
89+
Staple: c.Staple,
90+
})
91+
}
10192
}
102-
color.New(color.Bold, color.FgGreen).Fprintf(os.Stdout, " Code signing successful\n")
103-
104-
// Create a zip
105-
if cfg.Zip != nil {
106-
color.New(color.Bold).Fprintf(os.Stdout, "==> %s Creating Zip archive...\n", iconPackage)
107-
err = zip.Zip(context.Background(), &zip.Options{
108-
Files: cfg.Source,
109-
OutputPath: cfg.Zip.OutputPath,
110-
})
111-
if err != nil {
112-
fmt.Fprintf(os.Stdout, color.RedString("❗️ Error creating zip archive:\n\n%s\n", err))
93+
94+
if len(cfg.Source) > 0 {
95+
if cfg.Sign == nil {
96+
color.New(color.Bold, color.FgRed).Fprintf(os.Stdout,
97+
"❗️ `sign` configuration required with `source` set\n")
98+
color.New(color.FgRed).Fprintf(os.Stdout,
99+
"When you set the `source` configuration, you must also specify the\n"+
100+
"`sign` configuration to sign the input files.\n")
101+
return 1
102+
}
103+
} else {
104+
if cfg.Zip != nil {
105+
color.New(color.Bold, color.FgRed).Fprintf(os.Stdout,
106+
"❗️ `zip` can only be set while `source` is also set\n")
107+
color.New(color.FgRed).Fprintf(os.Stdout,
108+
"Zip packaging is only supported when `source` is specified. This is\n"+
109+
"because the `zip` option packages the source files. If there are no\n"+
110+
"source files specified, then there is nothing to package.\n")
113111
return 1
114112
}
115-
color.New(color.Bold, color.FgGreen).Fprintf(os.Stdout, " Zip archive created with signed files\n")
116113

117-
// Queue to notarize
118-
items = append(items, &item{Path: cfg.Zip.OutputPath})
114+
if cfg.Dmg != nil {
115+
color.New(color.Bold, color.FgRed).Fprintf(os.Stdout,
116+
"❗️ `dmg` can only be set while `source` is also set\n")
117+
color.New(color.FgRed).Fprintf(os.Stdout,
118+
"Dmg packaging is only supported when `source` is specified. This is\n"+
119+
"because the `dmg` option packages the source files. If there are no\n"+
120+
"source files specified, then there is nothing to package.\n")
121+
return 1
122+
}
119123
}
120124

121-
// Create a dmg
122-
if cfg.Dmg != nil {
123-
// First create the dmg itself. This passes in the signed files.
124-
color.New(color.Bold).Fprintf(os.Stdout, "==> %s Creating dmg...\n", iconPackage)
125-
color.New().Fprintf(os.Stdout, " This will open Finder windows momentarily.\n")
126-
err = dmg.Dmg(context.Background(), &dmg.Options{
127-
Files: cfg.Source,
128-
OutputPath: cfg.Dmg.OutputPath,
129-
VolumeName: cfg.Dmg.VolumeName,
130-
Logger: logger.Named("dmg"),
131-
})
132-
if err != nil {
133-
fmt.Fprintf(os.Stdout, color.RedString("❗️ Error creating dmg:\n\n%s\n", err))
134-
return 1
125+
// If we have no items to sign then its probably an error
126+
if len(cfg.Source) == 0 && len(cfg.Notarize) == 0 {
127+
color.New(color.Bold, color.FgRed).Fprintf(os.Stdout, "❗️ No source files specified\n")
128+
color.New(color.FgRed).Fprintf(os.Stdout,
129+
"Your configuration had an empty 'source' and empty 'notarize' values. This must be populated with\n"+
130+
"at least one file to sign, package, and notarize.\n")
131+
return 1
132+
}
133+
134+
// If we're in source mode, then sign & package as configured
135+
if len(cfg.Source) > 0 {
136+
if cfg.Sign != nil {
137+
// Perform codesigning
138+
color.New(color.Bold).Fprintf(os.Stdout, "==> %s Signing files...\n", iconSign)
139+
err = sign.Sign(context.Background(), &sign.Options{
140+
Files: cfg.Source,
141+
Identity: cfg.Sign.ApplicationIdentity,
142+
Logger: logger.Named("sign"),
143+
})
144+
if err != nil {
145+
fmt.Fprintf(os.Stdout, color.RedString("❗️ Error signing files:\n\n%s\n", err))
146+
return 1
147+
}
148+
color.New(color.Bold, color.FgGreen).Fprintf(os.Stdout, " Code signing successful\n")
135149
}
136-
color.New().Fprintf(os.Stdout, " Dmg file created: %s\n", cfg.Dmg.OutputPath)
137-
138-
// Next we need to sign the actual DMG as well
139-
color.New().Fprintf(os.Stdout, " Signing dmg...\n")
140-
err = sign.Sign(context.Background(), &sign.Options{
141-
Files: []string{cfg.Dmg.OutputPath},
142-
Identity: cfg.Sign.ApplicationIdentity,
143-
Logger: logger.Named("dmg"),
144-
})
145-
if err != nil {
146-
fmt.Fprintf(os.Stdout, color.RedString("❗️ Error signing dmg:\n\n%s\n", err))
147-
return 1
150+
151+
// Create a zip
152+
if cfg.Zip != nil {
153+
color.New(color.Bold).Fprintf(os.Stdout, "==> %s Creating Zip archive...\n", iconPackage)
154+
err = zip.Zip(context.Background(), &zip.Options{
155+
Files: cfg.Source,
156+
OutputPath: cfg.Zip.OutputPath,
157+
})
158+
if err != nil {
159+
fmt.Fprintf(os.Stdout, color.RedString("❗️ Error creating zip archive:\n\n%s\n", err))
160+
return 1
161+
}
162+
color.New(color.Bold, color.FgGreen).Fprintf(os.Stdout, " Zip archive created with signed files\n")
163+
164+
// Queue to notarize
165+
items = append(items, &item{Path: cfg.Zip.OutputPath})
148166
}
149-
color.New(color.Bold, color.FgGreen).Fprintf(os.Stdout, " Dmg created and signed\n")
150167

151-
// Queue to notarize
152-
items = append(items, &item{Path: cfg.Dmg.OutputPath, Staple: true})
168+
// Create a dmg
169+
if cfg.Dmg != nil && cfg.Sign != nil {
170+
// First create the dmg itself. This passes in the signed files.
171+
color.New(color.Bold).Fprintf(os.Stdout, "==> %s Creating dmg...\n", iconPackage)
172+
color.New().Fprintf(os.Stdout, " This will open Finder windows momentarily.\n")
173+
err = dmg.Dmg(context.Background(), &dmg.Options{
174+
Files: cfg.Source,
175+
OutputPath: cfg.Dmg.OutputPath,
176+
VolumeName: cfg.Dmg.VolumeName,
177+
Logger: logger.Named("dmg"),
178+
})
179+
if err != nil {
180+
fmt.Fprintf(os.Stdout, color.RedString("❗️ Error creating dmg:\n\n%s\n", err))
181+
return 1
182+
}
183+
color.New().Fprintf(os.Stdout, " Dmg file created: %s\n", cfg.Dmg.OutputPath)
184+
185+
// Next we need to sign the actual DMG as well
186+
color.New().Fprintf(os.Stdout, " Signing dmg...\n")
187+
err = sign.Sign(context.Background(), &sign.Options{
188+
Files: []string{cfg.Dmg.OutputPath},
189+
Identity: cfg.Sign.ApplicationIdentity,
190+
Logger: logger.Named("dmg"),
191+
})
192+
if err != nil {
193+
fmt.Fprintf(os.Stdout, color.RedString("❗️ Error signing dmg:\n\n%s\n", err))
194+
return 1
195+
}
196+
color.New(color.Bold, color.FgGreen).Fprintf(os.Stdout, " Dmg created and signed\n")
197+
198+
// Queue to notarize
199+
items = append(items, &item{Path: cfg.Dmg.OutputPath, Staple: true})
200+
}
153201
}
154202

155203
// If we have no items to notarize then its probably an error in the configuration.
156204
if len(items) == 0 {
157205
color.New(color.Bold, color.FgYellow).Fprintf(os.Stdout, "\n⚠️ No items to notarize\n")
158206
color.New(color.FgYellow).Fprintf(os.Stdout,
159-
"You must specify a 'zip' or 'dmg' section in your configuration to enable\n"+
160-
"packaging and notarization. Without these sections, gon will only sign your\n"+
161-
"input files.\n")
207+
"You must specify a 'notarize' section or a 'source' section plus a 'zip' or 'dmg' section "+
208+
"in your configuration to enable packaging and notarization. Without these sections, gon\n"+
209+
"will only sign your input files in 'source'.\n")
162210
return 0
163211
}
164212

165213
// Notarize
166214
color.New(color.Bold).Fprintf(os.Stdout, "==> %s Notarizing...\n", iconNotarize)
167215
if len(items) > 1 {
168-
for _, f := range items {
169-
color.New().Fprintf(os.Stdout, " Path: %s\n", f.Path)
170-
}
171216
color.New().Fprintf(os.Stdout, " Files will be notarized concurrently to optimize queue wait\n")
172217
}
218+
for _, f := range items {
219+
color.New().Fprintf(os.Stdout, " Path: %s\n", f.Path)
220+
}
173221

174222
// Build our prefixes
175223
prefixes := statusPrefixList(items)

internal/config/config.go

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,12 @@ type Config struct {
1010
// be anything, this is required by Apple.
1111
BundleId string `hcl:"bundle_id"`
1212

13+
// Notarize is a single file (usually a .pkg installer or zip)
14+
// that is ready for notarization as-is
15+
Notarize []Notarize `hcl:"notarize,block"`
16+
1317
// Sign are the settings for code-signing the binaries.
14-
Sign Sign `hcl:"sign,block"`
18+
Sign *Sign `hcl:"sign,block"`
1519

1620
// AppleId are the credentials to use to talk to Apple.
1721
AppleId AppleId `hcl:"apple_id,block"`
@@ -43,6 +47,20 @@ type AppleId struct {
4347
Provider string `hcl:"provider,optional"`
4448
}
4549

50+
// NOtarize are the options for notarizing a pre-built file.
51+
type Notarize struct {
52+
// Path is the path to the file to notarize. This can be any supported
53+
// filetype (dmg, pkg, app, zip).
54+
Path string `hcl:"path"`
55+
56+
// BundleId is the bundle ID to use for notarizing this package.
57+
// If this isn't specified then the root bundle_id is inherited.
58+
BundleId string `hcl:"bundle_id"`
59+
60+
// Staple, if true will staple the notarization ticket to the file.
61+
Staple bool `hcl:"staple,optional"`
62+
}
63+
4664
// Sign are the options for codesigning the binaries.
4765
type Sign struct {
4866
// ApplicationIdentity is the ID or name of the certificate to

internal/config/testdata/basic.hcl.golden

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@
33
(string) (len=11) "./terraform"
44
},
55
BundleId: (string) (len=28) "com.mitchellh.test.terraform",
6-
Sign: (config.Sign) {
6+
Notarize: ([]config.Notarize) <nil>,
7+
Sign: (*config.Sign)({
78
ApplicationIdentity: (string) (len=3) "foo"
8-
},
9+
}),
910
AppleId: (config.AppleId) {
1011
Username: (string) (len=21) "mitchellh@example.com",
1112
Password: (string) (len=5) "hello",
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
source = []
2+
bundle_id = "com.example.terraform"
3+
4+
notarize {
5+
path = "/path/to/terraform.pkg"
6+
bundle_id = "foo.bar"
7+
}
8+
9+
apple_id {
10+
username = "mitchellh@example.com"
11+
password = "hello"
12+
}

0 commit comments

Comments
 (0)