-
Notifications
You must be signed in to change notification settings - Fork 13
/
main.go
211 lines (185 loc) · 6.34 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
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
/*
Copyright 2024 Chainguard, Inc.
SPDX-License-Identifier: Apache-2.0
*/
package main
import (
"archive/tar"
"bytes"
"encoding/pem"
"flag"
"fmt"
"io"
"log"
"os"
"strings"
"github.com/google/go-containerregistry/pkg/authn"
"github.com/google/go-containerregistry/pkg/name"
v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/mutate"
"github.com/google/go-containerregistry/pkg/v1/remote"
"github.com/google/go-containerregistry/pkg/v1/stream"
)
var (
imageURL string
caCertFile string
caCertsImageURL string
destImageURL string
platformStr string
imageCertPath string
outputCerts string
replaceCerts bool
ownerUserID int
ownerGroupID int
)
func init() {
flag.StringVar(&imageURL, "image-url", "", "The URL of the image to append the CA certificates to")
flag.StringVar(&caCertFile, "ca-certs-file", "", "The path to the local CA certificates file")
flag.StringVar(&caCertsImageURL, "ca-certs-image-url", "", "The URL of an image to extract the CA certificates from")
flag.StringVar(&destImageURL, "dest-image-url", "", "The URL of the image to push the modified image to")
flag.StringVar(&platformStr, "platform", "linux/amd64", "The platform to build the image for")
flag.StringVar(&imageCertPath, "image-cert-path", "/etc/ssl/certs/ca-certificates.crt", "The path to the certificate file in the image (optional)")
flag.IntVar(&ownerUserID, "owner-user-id", 0, "The user ID of the owner of the certificate file in the image (optional)")
flag.IntVar(&ownerGroupID, "owner-group-id", 0, "The group ID of the owner of the certificate file in the image (optional)")
flag.StringVar(&outputCerts, "output-certs-path", "", "Output the (appended) certificates file from the image to a local file (optional)")
flag.BoolVar(&replaceCerts, "replace-certs", false, "Replace the certificates in the certificate file instead of appending them")
}
func usage() {
flag.Usage()
os.Exit(1)
}
func main() {
var platform v1.Platform
flag.Parse()
if imageURL == "" || destImageURL == "" || (caCertFile == "" && caCertsImageURL == "") {
usage()
}
if platformStr != "" {
p, err := v1.ParsePlatform(platformStr)
if err != nil {
log.Fatalf("Failed to parse platform: %s", err)
}
platform = *p
}
// Get the cert bytes
caCertBytes, err := getCertBytes(platform)
if err != nil {
log.Fatalf("Failed to get certificate bytes: %s", err)
}
// Sanity check to make sure the caCertBytes are actually a list of pem-encoded certificates
block, _ := pem.Decode(caCertBytes)
if block == nil || block.Type != "CERTIFICATE" {
log.Fatalf("Failed to find any certificates in %s", caCertFile)
}
img, err := fetchImage(imageURL, platform)
if err != nil {
log.Fatalf("Failed to fetch image %s: %s\n", imageURL, err)
}
newImg, err := newImage(img, caCertBytes)
if err != nil {
log.Fatalf("Failed to create new image: %s\n", err)
}
if outputCerts != "" {
if err := os.WriteFile(outputCerts, caCertBytes, 0644); err != nil {
log.Fatalf("Failed to write certificates to file %s: %s.\n", outputCerts, err)
}
}
newRef, err := name.ParseReference(destImageURL)
if err != nil {
log.Fatalf("Failed to parse destination image URL %s: %s\n", destImageURL, err)
}
// Push the modified image back to the registry
err = remote.Write(newRef, newImg, remote.WithAuthFromKeychain(authn.DefaultKeychain))
if err != nil {
log.Fatalf("Failed to push modified image %s: %s\n", newRef.String(), err)
}
fmt.Fprintf(os.Stderr, "Successfully appended CA certificates to image %s\n", newRef.String())
h, err := newImg.Digest()
if err != nil {
log.Fatalf("Failed to get digest of image %s: %s\n", newRef.String(), err)
}
fmt.Printf("%s@sha256:%s\n", newRef.String(), h.Hex)
}
// Fetch the remote image
func fetchImage(imageURL string, platform v1.Platform) (v1.Image, error) {
ref, err := name.ParseReference(imageURL)
if err != nil {
return nil, err
}
img, err := remote.Image(ref, remote.WithAuthFromKeychain(authn.DefaultKeychain), remote.WithPlatform((platform)))
if err != nil {
return nil, err
}
return img, nil
}
func getCertBytes(platform v1.Platform) ([]byte, error) {
// Read the certs either from a local file or a remote image
if caCertFile != "" {
// Read the contents of the local CA certificates file
caCertBytes, err := os.ReadFile(caCertFile)
if err != nil {
log.Fatalf("Failed to read CA certificates file %s: %s\n", caCertFile, err)
}
// Sanity check to make sure the caCertBytes are actually a list of pem-encoded certificates
block, _ := pem.Decode(caCertBytes)
if block == nil || block.Type != "CERTIFICATE" {
log.Fatalf("Failed to find any certificates in %s", caCertFile)
}
return caCertBytes, nil
} else {
// Fetch the remote image and its manifest
img, err := fetchImage(caCertsImageURL, platform)
if err != nil {
log.Fatalf("Failed to fetch image %s: %s\n", caCertsImageURL, err)
}
return extractCACerts(img)
}
}
// Extract the ca-certificates file from the remote image
func extractCACerts(img v1.Image) ([]byte, error) {
flattened := mutate.Extract(img)
tr := tar.NewReader(flattened)
defer flattened.Close()
// Read the files in the tar reader
for {
hdr, err := tr.Next()
if err == io.EOF {
break
}
if hdr.Name == imageCertPath || hdr.Name == strings.TrimPrefix(imageCertPath, "/") {
return io.ReadAll(tr)
}
}
return nil, fmt.Errorf("failed to find %s in remote image", imageCertPath)
}
func newImage(old v1.Image, caCertBytes []byte) (v1.Image, error) {
var newCaCertBytes []byte
if replaceCerts {
newCaCertBytes = caCertBytes
} else {
imgCaCertBytes, err := extractCACerts(old)
if err != nil {
log.Fatalf("Failed to extract CA certificates from image: %s\n", err)
}
newCaCertBytes = append(append(imgCaCertBytes, caCertBytes...), '\n')
}
// Create a new tar file with the modified ca-certificates file
buf := bytes.Buffer{}
newTar := tar.NewWriter(&buf)
newTar.WriteHeader(&tar.Header{
Name: imageCertPath,
Mode: 0644,
Size: int64(len(newCaCertBytes)),
Uid: ownerUserID,
Gid: ownerGroupID,
})
if _, err := newTar.Write(newCaCertBytes); err != nil {
return nil, err
}
newTar.Close()
newImg, err := mutate.Append(old, mutate.Addendum{Layer: stream.NewLayer(io.NopCloser(&buf))})
if err != nil {
return nil, fmt.Errorf("failed to append modified CA certificates to image: %s", err)
}
return newImg, nil
}