mirror of
https://github.com/drone-plugins/drone-docker.git
synced 2026-07-16 16:21:18 +08:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 73c2165418 |
@@ -0,0 +1,29 @@
|
|||||||
|
package docker
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/pem"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// decodePEMCertificates extracts DER-encoded CERTIFICATE blocks from a PEM
|
||||||
|
// (or PEM bundle). Non-certificate blocks are skipped. Returns an error when
|
||||||
|
// no certificate blocks are present.
|
||||||
|
func decodePEMCertificates(pemBytes []byte) ([][]byte, error) {
|
||||||
|
var ders [][]byte
|
||||||
|
rest := pemBytes
|
||||||
|
for {
|
||||||
|
var block *pem.Block
|
||||||
|
block, rest = pem.Decode(rest)
|
||||||
|
if block == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if block.Type != "CERTIFICATE" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ders = append(ders, block.Bytes)
|
||||||
|
}
|
||||||
|
if len(ders) == 0 {
|
||||||
|
return nil, fmt.Errorf("no CERTIFICATE PEM blocks found")
|
||||||
|
}
|
||||||
|
return ders, nil
|
||||||
|
}
|
||||||
+114
@@ -0,0 +1,114 @@
|
|||||||
|
package docker
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/pem"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Real self-signed CAs generated for these tests (CN=HarnessTestCA / HarnessTestCA2).
|
||||||
|
const harnessTestCAPEM = `-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDETCCAfmgAwIBAgIUUnMVT6JwuvsR8lPanhZxYJnqUPYwDQYJKoZIhvcNAQEL
|
||||||
|
BQAwGDEWMBQGA1UEAwwNSGFybmVzc1Rlc3RDQTAeFw0yNjA3MTUwNzMxMDBaFw0z
|
||||||
|
NjA3MTIwNzMxMDBaMBgxFjAUBgNVBAMMDUhhcm5lc3NUZXN0Q0EwggEiMA0GCSqG
|
||||||
|
SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDJOssgNxmGP8itq7IxObLY2igTK3/YeMIJ
|
||||||
|
9gvjWzUGe/RRrWqWJvfplKScNaIbyGaABYbDaGURnL3uGr5QQWEUbqvOMlEYaoNq
|
||||||
|
21TmVbEl6Sponc+4aFfDPP9CFAOn2tqyrZaU3wZIjSUWbGo2wwseWULzaQxZ8lZ6
|
||||||
|
a+kgjipymolfTZeHbJQAk/hwXVhbrz6xBbOhue/K/tFJxFBuawvwgXEi+2ywzXgy
|
||||||
|
fn80wYWdjeFEpFAtNKyjg+bu36DtHikwDS9XqZzgSZnGhDKEGh3cj0hd3/nhr59h
|
||||||
|
FCDm56XsBvWQVHmNmiuuxCn3svzSa9qdyk6tcIqZN8wKz1J7lFRfAgMBAAGjUzBR
|
||||||
|
MB0GA1UdDgQWBBSrLR7fG/bwSozrDipyfT5MtiuYjTAfBgNVHSMEGDAWgBSrLR7f
|
||||||
|
G/bwSozrDipyfT5MtiuYjTAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUA
|
||||||
|
A4IBAQAq9+CxKqe2vhBTz+5i9NBpxzxFy3fZTKcakuB98hafnNw29D4VggjV7sgo
|
||||||
|
99En87gkTIPsiOsRa0YH3wl5aa5EUp/Dx0ZfXasjzS5BFSZKZDYcuLuuUEeK9N6j
|
||||||
|
UBxaWDxNRjENevkX8SWpbxyNkcDGZM2IfJvVA/g7h8ERZLjlu9iOTqb6jt3PDe4q
|
||||||
|
CNoMbU5L3md3gk8mfoEZAX/IIUmbw7hAy7zE9dEfbKHeXl3FrcXPL0WmLePcK9BF
|
||||||
|
8XEpsOUSoq4sMPdVxaPF7l+SKDbdsR4gKtwlEMLefiiLqk0k4HhygjzzRgdsMOc3
|
||||||
|
S8dL7zv0BrAFy//XsfmsVeVFVhRN
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
`
|
||||||
|
|
||||||
|
const harnessTestCA2PEM = `-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDEzCCAfugAwIBAgIUR5AfE7MTe0ucAk1JzlqvOJd3Z7AwDQYJKoZIhvcNAQEL
|
||||||
|
BQAwGTEXMBUGA1UEAwwOSGFybmVzc1Rlc3RDQTIwHhcNMjYwNzE1MDczMTAwWhcN
|
||||||
|
MzYwNzEyMDczMTAwWjAZMRcwFQYDVQQDDA5IYXJuZXNzVGVzdENBMjCCASIwDQYJ
|
||||||
|
KoZIhvcNAQEBBQADggEPADCCAQoCggEBAI2EDM2Ac+jwhP8OaWFFb+DlVwzKSkEI
|
||||||
|
LoaZ41RQGHZaCckTLtaCtIBaNQHLWqj8q6I0fIz+8mmaXuxRKDjnhGl3AFIJ2wql
|
||||||
|
Hbyywo2MQcu+6wfs2Ewd887NDb5wrNJs/gl1GYRilSAd8n53T5IJUHHA0IGxtq4g
|
||||||
|
9XwS6OTmfBdQ3ycEsi9Yexd5Oz79sLMDhBnt21nYWrEO8Kumgyfd7gfQUBcGc1nH
|
||||||
|
NhoBrZffLmo+cljGqPNEFyAlWEnsmiqZnYh/EOUXGdXxsZMvyMWt02kLe0BlGmJr
|
||||||
|
PrI8iJCMElj86UhKK2oIjWLMA6cviYSkw9jyiihhodSUChhmqOABoe8CAwEAAaNT
|
||||||
|
MFEwHQYDVR0OBBYEFKK9RP95NSP4xmxN+hojPXlxVVatMB8GA1UdIwQYMBaAFKK9
|
||||||
|
RP95NSP4xmxN+hojPXlxVVatMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL
|
||||||
|
BQADggEBABlrORwXmhmO+eIdTHZMuad72JMEJeHxiRCGbwH6vAVb03YZAXLmIf31
|
||||||
|
sBKkUCDV+Vm0MaTYmYncK3eZZBn6teEWcx7+L9XlF7ckVKBqj9xkwFUdfVipCimD
|
||||||
|
cjBl/H+2PEY5Hzoy/uf2/sE1czrfHA2HeEIEacVyht9t0HTtqXQqrn44ijESkNtb
|
||||||
|
SqqaVYCcceS85ID2oJYYKQycMnFJd0VwSHlD+Lu4ZubXbrg+pzSqw6olTOwCE7Q2
|
||||||
|
LhLxQRB4f8Xw807aIuFK2mKbPs+4dmPkhClM3rbFzzldQJ4lKLnvZHcFtXJUXg2a
|
||||||
|
UzwP8JQn8xuz7gIZxMO3vGqTweP48yc=
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
`
|
||||||
|
|
||||||
|
func TestDecodePEMCertificates_Single(t *testing.T) {
|
||||||
|
ders, err := decodePEMCertificates([]byte(harnessTestCAPEM))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("decodePEMCertificates: %v", err)
|
||||||
|
}
|
||||||
|
if len(ders) != 1 {
|
||||||
|
t.Fatalf("got %d certs, want 1", len(ders))
|
||||||
|
}
|
||||||
|
cert, err := x509.ParseCertificate(ders[0])
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ParseCertificate: %v", err)
|
||||||
|
}
|
||||||
|
if cert.Subject.CommonName != "HarnessTestCA" {
|
||||||
|
t.Fatalf("CN = %q, want HarnessTestCA", cert.Subject.CommonName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDecodePEMCertificates_Bundle(t *testing.T) {
|
||||||
|
bundle := harnessTestCAPEM + "\n" + harnessTestCA2PEM
|
||||||
|
ders, err := decodePEMCertificates([]byte(bundle))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("decodePEMCertificates: %v", err)
|
||||||
|
}
|
||||||
|
if len(ders) != 2 {
|
||||||
|
t.Fatalf("got %d certs, want 2", len(ders))
|
||||||
|
}
|
||||||
|
for i, der := range ders {
|
||||||
|
if _, err := x509.ParseCertificate(der); err != nil {
|
||||||
|
t.Fatalf("cert %d: ParseCertificate: %v", i, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDecodePEMCertificates_SkipsNonCertificateBlocks(t *testing.T) {
|
||||||
|
keyPEM := pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: []byte("not-a-key")})
|
||||||
|
mixed := append(append([]byte{}, keyPEM...), []byte(harnessTestCAPEM)...)
|
||||||
|
ders, err := decodePEMCertificates(mixed)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("decodePEMCertificates: %v", err)
|
||||||
|
}
|
||||||
|
if len(ders) != 1 {
|
||||||
|
t.Fatalf("got %d certs, want 1 (private key block skipped)", len(ders))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDecodePEMCertificates_Empty(t *testing.T) {
|
||||||
|
_, err := decodePEMCertificates([]byte("not a pem\n"))
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected error for input with no CERTIFICATE blocks")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDecodePEMCertificates_MatchesOpenSSLDER(t *testing.T) {
|
||||||
|
ders, err := decodePEMCertificates([]byte(harnessTestCAPEM))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
origBlock, _ := pem.Decode([]byte(harnessTestCAPEM))
|
||||||
|
if !bytes.Equal(origBlock.Bytes, ders[0]) {
|
||||||
|
t.Fatal("decoded DER does not match original PEM payload")
|
||||||
|
}
|
||||||
|
}
|
||||||
+59
-27
@@ -5,8 +5,9 @@ package docker
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"unsafe"
|
||||||
"os/exec"
|
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
)
|
)
|
||||||
|
|
||||||
// installHarnessCA imports the Harness egress-proxy CA into the Windows
|
// installHarnessCA imports the Harness egress-proxy CA into the Windows
|
||||||
@@ -14,38 +15,69 @@ import (
|
|||||||
// Docker daemon and Go's crypto/x509 consult). Best-effort: failures are logged,
|
// Docker daemon and Go's crypto/x509 consult). Best-effort: failures are logged,
|
||||||
// never fatal.
|
// never fatal.
|
||||||
//
|
//
|
||||||
// certutil needs a file path, so the (already-validated) CA bytes are written to
|
// Uses CryptoAPI (crypt32.dll) directly so it works on Nano Server images that
|
||||||
// a temp .crt first, then added with `certutil -addstore -f Root <file>`.
|
// lack certutil and PowerShell. CERT_STORE_ADD_REPLACE_EXISTING keeps the step
|
||||||
|
// idempotent across retries.
|
||||||
func installHarnessCA(caPEM []byte) {
|
func installHarnessCA(caPEM []byte) {
|
||||||
tmp, err := os.CreateTemp("", "harness-egress-ca-*.crt")
|
ders, err := decodePEMCertificates(caPEM)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Could not create temp file for Harness CA: %s\n", err)
|
fmt.Printf("Could not decode Harness CA PEM: %s\n", err)
|
||||||
return
|
|
||||||
}
|
|
||||||
tmpPath := tmp.Name()
|
|
||||||
defer os.Remove(tmpPath)
|
|
||||||
|
|
||||||
if _, err := tmp.Write(caPEM); err != nil {
|
|
||||||
tmp.Close()
|
|
||||||
fmt.Printf("Could not write temp Harness CA: %s\n", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
tmp.Close()
|
|
||||||
|
|
||||||
if _, err := exec.LookPath("certutil"); err != nil {
|
|
||||||
fmt.Printf("certutil not found on PATH; cannot install Harness egress CA: %s\n", err)
|
|
||||||
fmt.Println("Base-image pulls through the egress proxy may fail with x509 errors.")
|
fmt.Println("Base-image pulls through the egress proxy may fail with x509 errors.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// -f overwrites an existing entry, keeping the step idempotent across retries.
|
storeName, err := windows.UTF16PtrFromString("ROOT")
|
||||||
cmd := exec.Command("certutil", "-addstore", "-f", "Root", tmpPath)
|
if err != nil {
|
||||||
cmd.Stdout = os.Stdout
|
fmt.Printf("Could not prepare Windows root store name: %s\n", err)
|
||||||
cmd.Stderr = os.Stderr
|
return
|
||||||
if err := cmd.Run(); err != nil {
|
}
|
||||||
fmt.Printf("Warning: certutil -addstore Root failed: %s\n", err)
|
|
||||||
|
store, err := windows.CertOpenStore(
|
||||||
|
windows.CERT_STORE_PROV_SYSTEM,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
windows.CERT_SYSTEM_STORE_LOCAL_MACHINE,
|
||||||
|
uintptr(unsafe.Pointer(storeName)),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Could not open LocalMachine\\Root store: %s\n", err)
|
||||||
fmt.Println("Base-image pulls through the egress proxy may fail with x509 errors.")
|
fmt.Println("Base-image pulls through the egress proxy may fail with x509 errors.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
fmt.Println("Installed Harness egress CA into the Windows LocalMachine\\Root store.")
|
defer windows.CertCloseStore(store, 0)
|
||||||
|
|
||||||
|
for i, der := range ders {
|
||||||
|
if err := addCertToStore(store, der); err != nil {
|
||||||
|
fmt.Printf("Warning: failed to add Harness CA cert %d to LocalMachine\\Root: %s\n", i+1, err)
|
||||||
|
fmt.Println("Base-image pulls through the egress proxy may fail with x509 errors.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("Installed %d Harness egress CA certificate(s) into the Windows LocalMachine\\Root store.\n", len(ders))
|
||||||
|
}
|
||||||
|
|
||||||
|
func addCertToStore(store windows.Handle, der []byte) error {
|
||||||
|
if len(der) == 0 {
|
||||||
|
return fmt.Errorf("empty DER certificate")
|
||||||
|
}
|
||||||
|
ctx, err := windows.CertCreateCertificateContext(
|
||||||
|
windows.X509_ASN_ENCODING|windows.PKCS_7_ASN_ENCODING,
|
||||||
|
&der[0],
|
||||||
|
uint32(len(der)),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("CertCreateCertificateContext: %w", err)
|
||||||
|
}
|
||||||
|
defer windows.CertFreeCertificateContext(ctx)
|
||||||
|
|
||||||
|
if err := windows.CertAddCertificateContextToStore(
|
||||||
|
store,
|
||||||
|
ctx,
|
||||||
|
windows.CERT_STORE_ADD_REPLACE_EXISTING,
|
||||||
|
nil,
|
||||||
|
); err != nil {
|
||||||
|
return fmt.Errorf("CertAddCertificateContextToStore: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ require (
|
|||||||
github.com/stretchr/testify v1.11.1
|
github.com/stretchr/testify v1.11.1
|
||||||
github.com/urfave/cli v1.22.2
|
github.com/urfave/cli v1.22.2
|
||||||
golang.org/x/oauth2 v0.34.0
|
golang.org/x/oauth2 v0.34.0
|
||||||
|
golang.org/x/sys v0.39.0
|
||||||
google.golang.org/api v0.187.0
|
google.golang.org/api v0.187.0
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -61,7 +62,6 @@ require (
|
|||||||
go.opentelemetry.io/otel/trace v1.39.0 // indirect
|
go.opentelemetry.io/otel/trace v1.39.0 // indirect
|
||||||
golang.org/x/crypto v0.46.0 // indirect
|
golang.org/x/crypto v0.46.0 // indirect
|
||||||
golang.org/x/net v0.48.0 // indirect
|
golang.org/x/net v0.48.0 // indirect
|
||||||
golang.org/x/sys v0.39.0 // indirect
|
|
||||||
golang.org/x/text v0.32.0 // indirect
|
golang.org/x/text v0.32.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect
|
||||||
google.golang.org/grpc v1.79.3 // indirect
|
google.golang.org/grpc v1.79.3 // indirect
|
||||||
|
|||||||
Reference in New Issue
Block a user