add support for notarization in fleetdm/fleetctl images (#6818)

#6674
This commit is contained in:
Roberto Dip
2022-07-25 20:06:10 -03:00
committed by GitHub
parent c09a8a7385
commit d63f56f8c0
7 changed files with 193 additions and 2 deletions
+24
View File
@@ -150,6 +150,30 @@ func packageCommand() *cli.Command {
EnvVars: []string{"FLEETCTL_NATIVE_TOOLING"},
Destination: &opt.NativeTooling,
},
&cli.StringFlag{
Name: "macos-devid-pem-content",
Usage: "Dev ID certificate keypair content in PEM format",
EnvVars: []string{"FLEETCTL_MACOS_DEVID_PEM_CONTENT"},
Destination: &opt.MacOSDevIDCertificateContent,
},
&cli.StringFlag{
Name: "app-store-connect-api-key-id",
Usage: "App Store Connect API key used for notarization",
EnvVars: []string{"FLEETCTL_APP_STORE_CONNECT_API_KEY_ID"},
Destination: &opt.AppStoreConnectAPIKeyID,
},
&cli.StringFlag{
Name: "app-store-connect-api-key-issuer",
Usage: "Issuer of the App Store Connect API key",
EnvVars: []string{"FLEETCTL_APP_STORE_CONNECT_API_KEY_ISSUER"},
Destination: &opt.AppStoreConnectAPIKeyIssuer,
},
&cli.StringFlag{
Name: "app-store-connect-api-key-content",
Usage: "Contents of the .p8 App Store Connect API key",
EnvVars: []string{"FLEETCTL_APP_STORE_CONNECT_API_KEY_CONTENT"},
Destination: &opt.AppStoreConnectAPIKeyContent,
},
},
Action: func(c *cli.Context) error {
if opt.FleetURL != "" || opt.EnrollSecret != "" {
+32 -2
View File
@@ -2,6 +2,7 @@ package packaging
import (
"bytes"
"errors"
"fmt"
"io/ioutil"
"os"
@@ -116,17 +117,46 @@ func BuildPkg(opt Options) (string, error) {
}
generatedPath := filepath.Join(tmpDir, "orbit.pkg")
isDarwin := runtime.GOOS == "darwin"
isLinuxNative := runtime.GOOS == "linux" && opt.NativeTooling
if len(opt.SignIdentity) != 0 {
if len(opt.MacOSDevIDCertificateContent) != 0 {
return "", errors.New("providing a sign identity and a Dev ID certificate is not supported")
}
log.Info().Str("identity", opt.SignIdentity).Msg("productsign package")
if err := signPkg(generatedPath, opt.SignIdentity); err != nil {
return "", fmt.Errorf("productsign: %w", err)
}
}
if isLinuxNative && len(opt.MacOSDevIDCertificateContent) > 0 {
if len(opt.SignIdentity) != 0 {
return "", errors.New("providing a sign identity and a Dev ID certificate is not supported")
}
if err := rSign(generatedPath, opt.MacOSDevIDCertificateContent); err != nil {
return "", fmt.Errorf("rcodesign: %w", err)
}
}
if opt.Notarize {
if err := NotarizeStaple(generatedPath, "com.fleetdm.orbit"); err != nil {
return "", err
switch {
case isDarwin:
if err := NotarizeStaple(generatedPath, "com.fleetdm.orbit"); err != nil {
return "", err
}
case isLinuxNative:
if len(opt.AppStoreConnectAPIKeyID) == 0 || len(opt.AppStoreConnectAPIKeyIssuer) == 0 {
return "", errors.New("both an App Store Connect API key and issuer must be set for native notarization")
}
if err := rNotarizeStaple(generatedPath, opt.AppStoreConnectAPIKeyID, opt.AppStoreConnectAPIKeyIssuer, opt.AppStoreConnectAPIKeyContent); err != nil {
return "", err
}
default:
return "", errors.New("notarization is not supported in this platform")
}
}
+79
View File
@@ -0,0 +1,79 @@
package packaging
import (
"bytes"
"fmt"
"os"
"os/exec"
"path/filepath"
"github.com/fleetdm/fleet/v4/pkg/secure"
)
func rSign(pkgPath, cert string) error {
pemPath := filepath.Join(os.TempDir(), "cert.pem")
defer os.Remove(pemPath)
err := os.WriteFile(pemPath, []byte(cert), 0o600)
if err != nil {
return fmt.Errorf("writing cert data: %e", err)
}
var outBuf bytes.Buffer
cmd := exec.Command(
"rcodesign",
"sign",
pkgPath,
"--pem-source", pemPath,
)
cmd.Stdout = &outBuf
cmd.Stderr = &outBuf
if err := cmd.Run(); err != nil {
fmt.Println(outBuf.String())
return fmt.Errorf("rcodesign: %w", err)
}
return nil
}
func rNotarizeStaple(pkg, apiKeyID, apiKeyIssuer, apiKeyContent string) error {
path, err := writeAPIKeys(apiKeyIssuer, apiKeyID, apiKeyContent)
defer os.Remove(path)
if err != nil {
return fmt.Errorf("writing API keys: %e", err)
}
var outBuf bytes.Buffer
cmd := exec.Command("rcodesign",
"notarize",
pkg,
"--api-issuer", apiKeyIssuer,
"--api-key", apiKeyID,
"--staple",
)
cmd.Stdout = &outBuf
cmd.Stderr = &outBuf
if err := cmd.Run(); err != nil {
fmt.Println(outBuf.String())
return fmt.Errorf("rcodesign notarize: %w", err)
}
return nil
}
func writeAPIKeys(issuer, id, content string) (string, error) {
homedir, err := os.UserHomeDir()
if err != nil {
return "", fmt.Errorf("finding home dir: %e", err)
}
// The underliying tools (rcodesign and Transporter) expect to find a
// certificate key in this path.
path := filepath.Join(homedir, ".appstoreconnect", "private_keys")
if err = secure.MkdirAll(path, 0o600); err != nil {
return "", fmt.Errorf("finding home dir: %e", err)
}
keyPath := filepath.Join(path, fmt.Sprintf("AuthKey_%s.p8", id))
if err = os.WriteFile(keyPath, []byte(content), 0o600); err != nil {
return "", fmt.Errorf("writing api key contents: %e", err)
}
return keyPath, nil
}
+11
View File
@@ -14,6 +14,10 @@ var macosPackageInfoTemplate = template.Must(template.New("").Option("missingkey
</pkg-info>
`))
// This template is used to generate a Distribution Definition file, which
// controls the experience of the installer (the default dir, what options the
// user has, etc.)
//
// Reference:
// https://developer.apple.com/library/archive/documentation/DeveloperTools/Reference/DistributionDefinitionRef/Chapters/Distribution_XML_Ref.html
var macosDistributionTemplate = template.Must(template.New("").Option("missingkey=error").Parse(
@@ -26,7 +30,14 @@ var macosDistributionTemplate = template.Must(template.New("").Option("missingke
<choice id="choiceBase" title="Fleet osquery" enabled="false" selected="true" description="Standard installation for Fleet osquery.">
<pkg-ref id="{{.Identifier}}.base.pkg"/>
</choice>
{{/* base.pkg specified here is the foldername that contains the package contents */}}
<pkg-ref id="{{.Identifier}}.base.pkg" version="{{.Version}}" auth="root">#base.pkg</pkg-ref>
{{/* this ref is collapsed with the previous, having a bundle version helps our notarization tools */}}
<pkg-ref id="{{.Identifier}}.base.pkg">
<bundle-version>
<bundle id="{{.Identifier}}" path="" />
</bundle-version>
</pkg-ref>
</installer-gui-script>
`))
+9
View File
@@ -66,6 +66,15 @@ type Options struct {
// Native tooling is used to determine if the package should be built
// natively instead of via Docker images.
NativeTooling bool
// MacOSDevIDCertificateContent is a string containing a PEM keypair used to
// sign a macOS package via NativeTooling
MacOSDevIDCertificateContent string
// AppStoreConnectAPIKeyID is the Appstore Connect API key provided by Apple
AppStoreConnectAPIKeyID string
// AppStoreConnectAPIKeyIssuer is the issuer of App Store API Key
AppStoreConnectAPIKeyIssuer string
// AppStoreConnectAPIKeyContent is the content of the App Store API Key
AppStoreConnectAPIKeyContent string
}
func initializeTempDir() (string, error) {
+10
View File
@@ -1,3 +1,11 @@
FROM rust:latest AS builder
ARG transporter_url=https://itunesconnect.apple.com/WebObjects/iTunesConnect.woa/ra/resources/download/public/Transporter__Linux/bin
RUN cargo install apple-codesign \
&& curl -sSf $transporter_url -o transporter_install.sh \
&& sh transporter_install.sh --target transporter --accept --noexec
FROM debian:stable-slim
RUN apt-get update \
@@ -9,6 +17,8 @@ RUN apt-get update \
# copy macOS dependencies
COPY --from=fleetdm/bomutils:latest /usr/bin/mkbom /usr/local/bin/xar /usr/bin/
COPY --from=fleetdm/bomutils:latest /usr/local/lib /usr/local/lib/
COPY --from=builder /transporter/itms /usr/local/
COPY --from=builder /usr/local/cargo/bin/rcodesign /usr/local/bin
# copy Windows dependencies
COPY --from=fleetdm/wix:latest /home/wine /home/wine
+28
View File
@@ -24,6 +24,34 @@ context to have access to the `fleetctl` binary. To build the image, run:
make fleetctl-docker
```
#### macOS signing + notarization
To sign and notarize a generated `pkg` you must have:
1. A Developer ID certificate in PEM format
2. An Apple Store Connect API key
> Note: the Developer ID certificate must be in PEM format because this image
> can be run in automated enviroments where secrets are passed via environment
> variables, and thus they must be in plain text.
>
> To convert a PKCS 12 certificate to PEM, you can run the following command:
>
> ```
> openssl pkcs12 -in /path/to/cert.p12 -out signing-keypair.pem -nodes
> ```
Once you are set, you can build and notarize/staple your package with:
```
docker run -v "$(pwd):/build" fleetdm/fleetctl package --type=pkg \
--macos-devid-pem-content="$(cat /path/to/signing-keypair.pem)" \
--notarize \
--app-store-connect-api-key-id="A6DX865SKS" \
--app-store-connect-api-key-issuer="68911d4c-110c-4172-b9f7-b7efa30f9680 " \
--app-store-connect-api-key-content="$(cat /path/to/AuthKey_A6DX865SKS.p8)"
```
### Publishing
There's a GitHub workflow to build and publish this image to Docker Hub, currently it has to be triggered [manually](https://docs.github.com/en/actions/managing-workflow-runs/manually-running-a-workflow).