diff --git a/.gitignore b/.gitignore index 17ad19e97e..30a912a21a 100644 --- a/.gitignore +++ b/.gitignore @@ -10,5 +10,7 @@ __pycache__ /test/data/manifests /tools/appsre-ansible/inventory dictionary.dic +/cmd/ostree-resolve/*.crt +/cmd/ostree-resolve/*.key *~ diff --git a/cmd/ostree-resolve/main.go b/cmd/ostree-resolve/main.go new file mode 100644 index 0000000000..f56e8cb9af --- /dev/null +++ b/cmd/ostree-resolve/main.go @@ -0,0 +1,41 @@ +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")) + + if len(os.Args) != 3 { + fmt.Println("Usage: ostree-resolve ") + os.Exit(1) + } + + spec := ostree.SourceSpec{ + URL: os.Args[1], + Ref: os.Args[2], + } + 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) +} diff --git a/pkg/ostree/ostree.go b/pkg/ostree/ostree.go index 8898f33702..faa0976de6 100644 --- a/pkg/ostree/ostree.go +++ b/pkg/ostree/ostree.go @@ -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 } @@ -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{} + + // 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) @@ -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 @@ -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 @@ -252,7 +272,7 @@ 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 } @@ -260,3 +280,43 @@ func Resolve(source SourceSpec) (CommitSpec, error) { } 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 + } +}