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

IgnorePathIssues config option to treat some issues as non-fatal #14

Closed
wants to merge 1 commit into from
Closed
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
33 changes: 33 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ gon helps you automate the process of notarization.
- [Prerequisite: Acquiring a Developer ID Certificate](#prerequisite-acquiring-a-developer-id-certificate)
- [Configuration File](#configuration-file)
- [Notarization-Only Configuration](#notarization-only-configuration)
- [Handling Notarization Issues](#handling-notarization-issues)
- [Processing Time](#processing-time)
- [Using within Automation](#using-within-automation)
- [Machine-Readable Output](#machine-readable-output)
Expand Down Expand Up @@ -310,6 +311,38 @@ apple_id {
Note you may specify multiple `notarize` blocks to notarize multipel files
concurrently.

### Handling Notarization Issues

After notarization, Apple sends a log containing what actions it performed. The
log may contain a list of "issues", which may or may not be fatal. In some
circumstances, a successful notarization can still produce a bundle that does
not pass Gatekeeper and will fail to install. By default, `gon` will treat any
issues reported by Apple as fatal due to the likelihood of an non-installable
bundle.

If you know that some issues reported by Apple that are non-fatal, such as an
executable included in your bundle that is not essential for the install, you
can treat these issues as non-fatal and `gon` will not fail. The
`ignorable_path_issues` configuration option allows you to supply a regular
expression that matches the `path` attached to the issues. The `path` reported
by Apple normally has the format `<bundle> <file path>` so your regular
expression should take this into account. Multiple paths should be composed
into a single regular expression by broad matches or `|`. Supplying `".*"` will
treat all issues as non-fatal (caveat emptor!).

```hcl
notarize {
path = "/path/to/terraform.pkg"
...
}

apple_id {
...
}

ignorable_path_issues = "^terraform.pkg Contents/Payload/usr/lib/inconsequential/boop$"
```

### Processing Time

The notarization process requires submitting your package(s) to Apple
Expand Down
69 changes: 46 additions & 23 deletions cmd/gon/item.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"fmt"
"os"
"regexp"
"sync"

"github.com/fatih/color"
Expand Down Expand Up @@ -128,29 +129,7 @@ func (i *item) notarize(ctx context.Context, opts *processOptions) error {
)
}

// If we have any issues then it is a failed notarization. Notarization
// can "succeed" with warnings, but when you attempt to use/open a file
// Gatekeeper rejects it. So we currently reject any and all issues.
if len(log.Issues) > 0 {
var err error

lock.Lock()
color.New(color.FgRed).Fprintf(os.Stdout,
" %s%d issues during notarization:\n",
opts.Prefix, len(log.Issues))
for idx, issue := range log.Issues {
color.New(color.FgRed).Fprintf(os.Stdout,
" %sIssue #%d (%s) for path %q: %s\n",
opts.Prefix, idx+1, issue.Severity, issue.Path, issue.Message)

// Append the error so we can return it
err = multierror.Append(err, fmt.Errorf(
"%s for path %q: %s",
issue.Severity, issue.Path, issue.Message,
))
}
lock.Unlock()

if err := handleIssues(opts, log); err != nil {
return err
}
}
Expand Down Expand Up @@ -198,6 +177,50 @@ func (i *item) notarize(ctx context.Context, opts *processOptions) error {
return nil
}

func handleIssues(opts *processOptions, log *notarize.Log) error {
// If we have any issues then it is a failed notarization. Notarization
// can "succeed" with warnings, but when you attempt to use/open a file
// Gatekeeper rejects it. So we currently reject any and all issues.

lock := opts.OutputLock
if len(log.Issues) > 0 {
var err multierror.Error

lock.Lock()
color.New(color.FgRed).Fprintf(os.Stdout,
" %s%d issues during notarization:\n",
opts.Prefix, len(log.Issues))
for idx, issue := range log.Issues {
color.New(color.FgRed).Fprintf(os.Stdout,
" %sIssue #%d (%s) for path %q: %s\n",
opts.Prefix, idx+1, issue.Severity, issue.Path, issue.Message)

if opts.Config.IgnorePathIssues != nil {
matched, err := regexp.MatchString(*opts.Config.IgnorePathIssues, issue.Path)
if err != nil {
return err // poorly formatted regex?
}
if matched {
continue
}
}
// Append the error so we can return it
err = *multierror.Append(&err, fmt.Errorf(
"%s for path %q: %s",
issue.Severity, issue.Path, issue.Message,
))
}
lock.Unlock()

if len(err.WrappedErrors()) == 0 {
return nil
}
return &err
}

return nil
}

// String implements Stringer
func (i *item) String() string {
result := i.Path
Expand Down
174 changes: 174 additions & 0 deletions cmd/gon/item_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
package main

import (
"sync"
"testing"

"github.com/hashicorp/go-hclog"
"github.com/hashicorp/go-multierror"
"github.com/mitchellh/gon/internal/config"
"github.com/mitchellh/gon/notarize"
"github.com/stretchr/testify/require"
)

func TestHandleIssues_NoIssues(t *testing.T) {
// single issue
verifyHandleIssues(t, config.Config{})
}

func TestHandleIssues_DefaultIssues(t *testing.T) {
// single issue
verifyHandleIssues(t, config.Config{}, logAndExpectedError{
issue: notarize.LogIssue{
Severity: "borked",
Path: "pkg foo/bar/baz",
Message: "nope nope nope",
},
expectedError: "borked for path \"pkg foo/bar/baz\": nope nope nope",
})

// two issues
verifyHandleIssues(t,
config.Config{},
logAndExpectedError{
issue: notarize.LogIssue{
Severity: "borked",
Path: "pkg foo/bar/baz",
Message: "nope nope nope",
},
expectedError: "borked for path \"pkg foo/bar/baz\": nope nope nope",
},
logAndExpectedError{
issue: notarize.LogIssue{
Severity: "verysevere",
Path: "pkg foo/bar/bang",
Message: "no, just no",
},
expectedError: "verysevere for path \"pkg foo/bar/bang\": no, just no",
},
)
}

func TestHandleIssues_IgnoreAllIssues(t *testing.T) {
regex := "^pkg foo/bar/.*$"

// single issue
verifyHandleIssues(t,
config.Config{IgnorePathIssues: &regex},
logAndExpectedError{
issue: notarize.LogIssue{
Severity: "borked",
Path: "pkg foo/bar/baz",
Message: "nope nope nope",
},
},
)

// two issues
verifyHandleIssues(t,
config.Config{IgnorePathIssues: &regex},
logAndExpectedError{
issue: notarize.LogIssue{
Severity: "borked",
Path: "pkg foo/bar/baz",
Message: "nope nope nope",
},
},
logAndExpectedError{
issue: notarize.LogIssue{
Severity: "verysevere",
Path: "pkg foo/bar/bang",
Message: "no, just no",
},
},
)
}

func TestHandleIssues_IgnoreSomeIssues(t *testing.T) {
regex := "^pkg foo/bar/baz$"

// single issue
verifyHandleIssues(t,
config.Config{IgnorePathIssues: &regex},
logAndExpectedError{
issue: notarize.LogIssue{
Severity: "borked",
Path: "pkg foo/bar/baz",
Message: "nope nope nope",
},
},
)

// two issues
verifyHandleIssues(t,
config.Config{IgnorePathIssues: &regex},
logAndExpectedError{
issue: notarize.LogIssue{
Severity: "borked",
Path: "pkg foo/bar/baz",
Message: "nope nope nope",
},
},
logAndExpectedError{
issue: notarize.LogIssue{
Severity: "verysevere",
Path: "pkg foo/bar/bang",
Message: "no, just no",
},
expectedError: "verysevere for path \"pkg foo/bar/bang\": no, just no",
},
)
}

type logAndExpectedError struct {
issue notarize.LogIssue
expectedError string
}

func expectedErrors(logAndExpectedErrors []logAndExpectedError) int {
var ee = 0
for _, lee := range logAndExpectedErrors {
if lee.expectedError != "" {
ee++
}
}
return ee
}

func verifyHandleIssues(t *testing.T, cfg config.Config, logAndExpectedErrors ...logAndExpectedError) {
logger := hclog.L()

issues := make([]notarize.LogIssue, len(logAndExpectedErrors))
for idx, lee := range logAndExpectedErrors {
issues[idx] = lee.issue
}

var lock sync.Mutex
log := notarize.Log{Issues: issues}

opts := processOptions{
Config: &cfg,
Logger: logger,
Prefix: "TestHandleIssues",
OutputLock: &lock,
}

err := handleIssues(&opts, &log)

if expectedErrors(logAndExpectedErrors) == 0 {
require.NoError(t, err)
} else {
require.Error(t, err)

me, ok := err.(*multierror.Error)
require.True(t, ok)
require.Len(t, me.WrappedErrors(), expectedErrors(logAndExpectedErrors))
idx := 0
for _, lee := range logAndExpectedErrors {
if lee.expectedError != "" {
require.EqualError(t, me.WrappedErrors()[idx], lee.expectedError)
idx++
}
}
}
}
7 changes: 7 additions & 0 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,13 @@ type Config struct {
// Dmg, if present, creates a dmg file to package the signed `Source` files
// into. Dmg files support stapling so this allows offline usage.
Dmg *Dmg `hcl:"dmg,block"`

// IgnorePathIssues, if present, will allow a notarization to succeed in the
// presence of issues reported by Apple. Supply a regular expression to match
// against the path(s) for which issues should be considered non-fatal, or
// ".*" to match all issues.
// Note that paths reported by Apple take the format: "<bundle> <file>".
IgnorePathIssues *string `hcl:"ignorable_path_issues,optional"`
}

// AppleId are the authentication settings for Apple systems.
Expand Down
3 changes: 2 additions & 1 deletion internal/config/testdata/basic.hcl.golden
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,6 @@
Provider: (string) ""
}),
Zip: (*config.Zip)(<nil>),
Dmg: (*config.Dmg)(<nil>)
Dmg: (*config.Dmg)(<nil>),
IgnorePathIssues: (*string)(<nil>)
})
3 changes: 2 additions & 1 deletion internal/config/testdata/entitle.hcl.golden
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,6 @@
Provider: (string) ""
}),
Zip: (*config.Zip)(<nil>),
Dmg: (*config.Dmg)(<nil>)
Dmg: (*config.Dmg)(<nil>),
IgnorePathIssues: (*string)(<nil>)
})
3 changes: 2 additions & 1 deletion internal/config/testdata/env_appleid.hcl.golden
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@
}),
AppleId: (*config.AppleId)(<nil>),
Zip: (*config.Zip)(<nil>),
Dmg: (*config.Dmg)(<nil>)
Dmg: (*config.Dmg)(<nil>),
IgnorePathIssues: (*string)(<nil>)
})
3 changes: 2 additions & 1 deletion internal/config/testdata/notarize.hcl.golden
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,6 @@
Provider: (string) ""
}),
Zip: (*config.Zip)(<nil>),
Dmg: (*config.Dmg)(<nil>)
Dmg: (*config.Dmg)(<nil>),
IgnorePathIssues: (*string)(<nil>)
})
3 changes: 2 additions & 1 deletion internal/config/testdata/notarize_multiple.hcl.golden
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,6 @@
Provider: (string) ""
}),
Zip: (*config.Zip)(<nil>),
Dmg: (*config.Dmg)(<nil>)
Dmg: (*config.Dmg)(<nil>),
IgnorePathIssues: (*string)(<nil>)
})