Skip to content

Commit

Permalink
ostree: configurable MTLS config for ostree resolve
Browse files Browse the repository at this point in the history
  • Loading branch information
lzap committed Oct 16, 2024
1 parent 6bacc69 commit 379ffe3
Show file tree
Hide file tree
Showing 3 changed files with 141 additions and 43 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,7 @@ __pycache__
/test/data/manifests
/tools/appsre-ansible/inventory
dictionary.dic
/cmd/ostree-resolve/*.crt
/cmd/ostree-resolve/*.key

*~
36 changes: 36 additions & 0 deletions cmd/ostree-resolve/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package main

import (
"fmt"
"net/url"
"os"

"github.com/osbuild/images/pkg/ostree"
)

func main() {
fmt.Println("Resolving ostree source, configuration:")
fmt.Printf("CA: %s\n", os.Getenv("OSBUILD_SOURCES_OSTREE_SSL_CA_CERT"))
fmt.Printf("Client cert: %s\n", os.Getenv("OSBUILD_SOURCES_OSTREE_SSL_CLIENT_CERT"))
fmt.Printf("Client key: %s\n", os.Getenv("OSBUILD_SOURCES_OSTREE_SSL_CLIENT_KEY"))
fmt.Printf("Proxy: %s\n", os.Getenv("OSBUILD_SOURCES_OSTREE_PROXY"))

spec := ostree.SourceSpec{
URL: "https://builder.home.lan/ccb2194f-9876-4e76-9e64-a338a32df230/",
Ref: "fedora/40/x86_64/iot",
}
proxy, err := url.Parse(os.Getenv("OSBUILD_SOURCES_OSTREE_PROXY"))
if err != nil {
panic(err)
}
cs, err := ostree.Resolve(
spec,
ostree.WithResolveCA(os.Getenv("OSBUILD_SOURCES_OSTREE_SSL_CA_CERT")),
ostree.WithResolveMTLSClient(os.Getenv("OSBUILD_SOURCES_OSTREE_SSL_CLIENT_CERT"), os.Getenv("OSBUILD_SOURCES_OSTREE_SSL_CLIENT_KEY")),
ostree.WithResolveProxy(proxy),
)
if err != nil {
panic(err)
}
fmt.Printf("Resolved checksum: %s", cs.Checksum)
}
146 changes: 103 additions & 43 deletions pkg/ostree/ostree.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,9 @@ var (
// SourceSpec serves as input for ResolveParams, and contains all necessary
// variables to resolve a ref, which can then be turned into a CommitSpec.
type SourceSpec struct {
URL string
Ref string
URL string
Ref string
// RHSM is deprecated, use Resolve function with MTLS configuration
RHSM bool
}

Expand Down Expand Up @@ -138,59 +139,50 @@ func verifyChecksum(commit string) bool {
return len(commit) > 0 && ostreeCommitRE.MatchString(commit)
}

// ResolveRef resolves the URL path specified by the location and ref
// ResolveRefMTLS resolves the URL path specified by the location and ref
// (location+"refs/heads/"+ref) and returns the commit ID for the named ref. If
// there is an error, it will be of type ResolveRefError.
func ResolveRef(location, ref string, consumerCerts bool, subs *rhsm.Subscriptions, ca *string) (string, error) {
// MTLS client cert, CA and HTTPS proxy are required for this function.
func ResolveRefMTLS(location, ref, caCert, cert, key string, proxy *url.URL) (string, error) {
u, err := url.Parse(location)
if err != nil {
return "", NewResolveRefError("error parsing ostree repository location: %v", err)
}
u.Path = path.Join(u.Path, "refs/heads/", ref)

var client *http.Client
if consumerCerts {
if subs == nil {
subs, err = rhsm.LoadSystemSubscriptions()
transport := http.DefaultTransport.(*http.Transport).Clone()
client := &http.Client{
Transport: transport,
Timeout: 300 * time.Second,
}
if u.Scheme == "https" {
tlsConf := &tls.Config{}

Check failure on line 159 in pkg/ostree/ostree.go

View workflow job for this annotation

GitHub Actions / ⌨ Lint

G402: TLS MinVersion too low. (gosec)

// If CA is set, load the CA certificate and add it to the TLS configuration. Otherwise, use the system CA.
if caCert != "" {
caCertPEM, err := os.ReadFile(caCert)
if err != nil {
return "", NewResolveRefError("error adding rhsm certificates when resolving ref: %s", err)
return "", NewResolveRefError("error adding ca certificate when resolving ref: %s", err)
}
if subs.Consumer == nil {
return "", NewResolveRefError("error adding rhsm certificates when resolving ref")
tlsConf.RootCAs = x509.NewCertPool()
if ok := tlsConf.RootCAs.AppendCertsFromPEM(caCertPEM); !ok {
return "", NewResolveRefError("error adding ca certificate when resolving ref")
}
}

tlsConf := &tls.Config{
MinVersion: tls.VersionTLS12,
}

if ca != nil {
caCertPEM, err := os.ReadFile(*ca)
if cert != "" && key != "" {
cert, err := tls.LoadX509KeyPair(cert, key)
if err != nil {
return "", NewResolveRefError("error adding rhsm certificates when resolving ref: %s", err)
return "", NewResolveRefError("error adding client certificate when resolving ref: %s", err)
}
roots := x509.NewCertPool()
ok := roots.AppendCertsFromPEM(caCertPEM)
if !ok {
return "", NewResolveRefError("error adding rhsm certificates when resolving ref")
}
tlsConf.RootCAs = roots
tlsConf.Certificates = []tls.Certificate{cert}
}

cert, err := tls.LoadX509KeyPair(subs.Consumer.ConsumerCert, subs.Consumer.ConsumerKey)
if err != nil {
return "", NewResolveRefError("error adding rhsm certificates when resolving ref: %s", err)
}
tlsConf.Certificates = []tls.Certificate{cert}
transport.TLSClientConfig = tlsConf
}

client = &http.Client{
Transport: &http.Transport{
TLSClientConfig: tlsConf,
},
Timeout: 300 * time.Second,
}
} else {
client = &http.Client{}
transport.Proxy = func(request *http.Request) (*url.URL, error) {
return proxy, nil
}

req, err := http.NewRequest(http.MethodGet, u.String(), nil)
Expand Down Expand Up @@ -218,6 +210,33 @@ func ResolveRef(location, ref string, consumerCerts bool, subs *rhsm.Subscriptio
return checksum, nil
}

// ResolveRef is deprecated, use ResolveRefMTLS.
func ResolveRef(location, ref string, consumerCerts bool, subs *rhsm.Subscriptions, ca *string) (string, error) {
var err error
var caCert, consumerCert, consumerKey string

if consumerCerts {
if subs == nil {
subs, err = rhsm.LoadSystemSubscriptions()
if err != nil {
return "", NewResolveRefError("error adding rhsm certificates when resolving ref: %s", err)
}
if subs.Consumer == nil {
return "", NewResolveRefError("error adding rhsm certificates when resolving ref")
}
}

consumerCert = subs.Consumer.ConsumerCert
consumerKey = subs.Consumer.ConsumerKey
}

if ca != nil {
caCert = *ca
}

return ResolveRefMTLS(location, ref, caCert, consumerCert, consumerKey, nil)
}

// Resolve the ostree source specification to a commit specification.
//
// If a URL is defined in the source specification, the checksum of the ref is
Expand All @@ -228,16 +247,17 @@ func ResolveRef(location, ref string, consumerCerts bool, subs *rhsm.Subscriptio
// resolved or checked against the repository.
//
// If the ref is malformed, the function returns with a RefError.
func Resolve(source SourceSpec) (CommitSpec, error) {
func Resolve(source SourceSpec, opt ...ResolveOptionFn) (CommitSpec, error) {
options := ResolveOptions{}
for _, o := range opt {
o(&options)
}

commit := CommitSpec{
Ref: source.Ref,
URL: source.URL,
}

if source.RHSM {
commit.Secrets = "org.osbuild.rhsm.consumer"
}

if verifyChecksum(source.Ref) {
// the ref is a commit: return as is
commit.Checksum = source.Ref
Expand All @@ -252,11 +272,51 @@ func Resolve(source SourceSpec) (CommitSpec, error) {
// URL set: Resolve checksum
if source.URL != "" {
// If a URL is specified, we need to fetch the commit at the URL.
checksum, err := ResolveRef(source.URL, source.Ref, source.RHSM, nil, nil)
checksum, err := ResolveRefMTLS(source.URL, source.Ref, options.CA, options.MTLSClientCert, options.MTLSClientKey, options.Proxy)
if err != nil {
return CommitSpec{}, err // ResolveRefError
}
commit.Checksum = checksum
}
return commit, nil
}

// ResolveOptions contains the options for resolving an ostree source.
type ResolveOptions struct {
BaseURL *url.URL
CA string
MTLSClientKey string
MTLSClientCert string
Proxy *url.URL
}

type ResolveOptionFn func(*ResolveOptions)

// WithResolveBaseURL sets the base URL for the ostree source.
func WithResolveBaseURL(baseURL *url.URL) ResolveOptionFn {
return func(o *ResolveOptions) {
o.BaseURL = baseURL
}
}

// WithResolveCA sets the CA certificate for the ostree source.
func WithResolveCA(ca string) ResolveOptionFn {
return func(o *ResolveOptions) {
o.CA = ca
}
}

// WithResolveMTLSClient sets the mTLS client certificate and key for the ostree source.
func WithResolveMTLSClient(cert, key string) ResolveOptionFn {
return func(o *ResolveOptions) {
o.MTLSClientCert = cert
o.MTLSClientKey = key
}
}

// WithResolveProxy sets the HTTPS proxy for the ostree source.
func WithResolveProxy(proxy *url.URL) ResolveOptionFn {
return func(o *ResolveOptions) {
o.Proxy = proxy
}
}

0 comments on commit 379ffe3

Please sign in to comment.