/
main.go
135 lines (116 loc) · 4.58 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
/*
* Teleport
* Copyright (C) 2023 Gravitational, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package main
import (
"debug/macho"
"fmt"
"github.com/alecthomas/kingpin/v2"
"github.com/gravitational/trace"
"github.com/sirupsen/logrus"
)
const binaryArgName string = "binaries"
// Default values for flags.
// TODO(camh): Remove when all call sites pass the correct values.
const (
DeveloperIdentity string = "0FFD3E3413AB4C599C53FBB1D8CA690915E33D83"
BundleID string = "com.gravitational.teleport"
)
type Config struct {
LogLevel string
LogJSON bool
AppleUsername string
ApplePassword string
DeveloperID string
BundleID string
BinaryPaths []string
}
func main() {
var config Config
kingpin.Flag("log-level", "Output logging level").Default(logrus.InfoLevel.String()).EnumVar(&config.LogLevel, getLogLevelStrings()...)
kingpin.Flag("log-json", "Enable JSON logging").Default(fmt.Sprintf("%v", false)).BoolVar(&config.LogJSON)
kingpin.Flag("apple-username", "Apple Connect username used for notarization").Required().Envar("APPLE_USERNAME").StringVar(&config.AppleUsername)
kingpin.Flag("apple-password", "Apple Connect password used for notarization").Required().Envar("APPLE_PASSWORD").StringVar(&config.ApplePassword)
kingpin.Flag("developer-id", "Key ID for signing binaries").Default(DeveloperIdentity).StringVar(&config.DeveloperID)
kingpin.Flag("bundle-id", "Bundle ID of application").Default(BundleID).StringVar(&config.BundleID)
kingpin.Arg(binaryArgName, "Path to Apple binaries for signing and notarization").Required().Action(binaryArgValidatiorAction).ExistingFilesVar(&config.BinaryPaths)
kingpin.Parse()
err := run(&config)
if err != nil {
logrus.Fatal(err.Error())
}
}
func getLogLevelStrings() []string {
logLevelStrings := make([]string, 0, len(logrus.AllLevels))
for _, level := range logrus.AllLevels {
logLevelStrings = append(logLevelStrings, level.String())
}
return logLevelStrings
}
func binaryArgValidatiorAction(pc *kingpin.ParseContext) error {
for _, element := range pc.Elements {
if clause, ok := element.Clause.(*kingpin.ArgClause); !ok || clause.Model().Name != binaryArgName {
continue
}
binaryPath := *element.Value
err := verifyFileIsAppleBinary(binaryPath)
if err != nil {
return trace.Wrap(err, "failed to verify that %q is a valid Apple binary for signing", binaryPath)
}
}
return nil
}
// Returns an error if the file is not a valid Apple binary
// Effectively does `file $BINARY | grep -ic 'mach-o'`
func verifyFileIsAppleBinary(filePath string) error {
// First check to see if the binary is a typical mach-o binary.
// If it's not, it could still be a multiarch "fat" mach-o binary,
// so we try that next. If both fail then the file is not an Apple
// binary.
fileHandle, err := macho.Open(filePath)
if err != nil {
fatFileHandle, err := macho.OpenFat(filePath)
if err != nil {
return trace.Wrap(err, "the provided file %q is neither a normal or multiarch mach-o binary.", filePath)
}
err = fatFileHandle.Close()
if err != nil {
return trace.Wrap(err, "identified %q as a multiarch mach-o binary but failed to close the file handle", filePath)
}
} else {
err := fileHandle.Close()
if err != nil {
return trace.Wrap(err, "identified %q as a mach-o binary but failed to close the file handle", filePath)
}
}
return nil
}
func run(config *Config) error {
// This needs to be called as soon as possible so that the logger can
// be used when checking args
parsedLogLevel, err := logrus.ParseLevel(config.LogLevel)
if err != nil {
// This should never be hit if kingpin is configured correctly
return trace.Wrap(err, "failed to parse logrus log level")
}
NewLoggerConfig(parsedLogLevel, config.LogJSON).setupLogger()
err = NewGonWrapper(config.AppleUsername, config.ApplePassword, config.DeveloperID, config.BundleID, config.BinaryPaths).SignAndNotarizeBinaries()
if err != nil {
return trace.Wrap(err, "failed to sign and notarize binaries")
}
return nil
}