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 15, 2024
1 parent 347abb4 commit 3b8187d
Show file tree
Hide file tree
Showing 9 changed files with 121 additions and 73 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)
}
3 changes: 0 additions & 3 deletions cmd/otk/osbuild-resolve-ostree-commit/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,6 @@ type Input struct {

// Ref to resolve.
Ref string `json:"ref"`

// Whether to use RHSM secrets when resolving and fetching the commit.
RHSM bool `json:"rhsm,omitempty"`
}

// Output contains everything needed to write a manifest that requires pulling
Expand Down
3 changes: 0 additions & 3 deletions internal/cmdutil/mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,5 @@ func MockOSTreeResolve(commitSource ostree.SourceSpec) ostree.CommitSpec {
URL: commitSource.URL,
Checksum: checksum,
}
if commitSource.RHSM {
spec.Secrets = "org.osbuild.rhsm.consumer"
}
return spec
}
10 changes: 4 additions & 6 deletions pkg/distro/fedora/images.go
Original file line number Diff line number Diff line change
Expand Up @@ -812,9 +812,8 @@ func makeOSTreeParentCommit(options *ostree.ImageOptions, defaultRef string) (*o

}
parentCommit = &ostree.SourceSpec{
URL: options.URL,
Ref: parentRef,
RHSM: options.RHSM,
URL: options.URL,
Ref: parentRef,
}
return parentCommit, commitRef
}
Expand All @@ -834,9 +833,8 @@ func makeOSTreePayloadCommit(options *ostree.ImageOptions, defaultRef string) (o
}

return ostree.SourceSpec{
URL: options.URL,
Ref: commitRef,
RHSM: options.RHSM,
URL: options.URL,
Ref: commitRef,
}, nil
}

Expand Down
10 changes: 4 additions & 6 deletions pkg/distro/rhel/images.go
Original file line number Diff line number Diff line change
Expand Up @@ -779,9 +779,8 @@ func makeOSTreeParentCommit(options *ostree.ImageOptions, defaultRef string) (*o

}
parentCommit = &ostree.SourceSpec{
URL: options.URL,
Ref: parentRef,
RHSM: options.RHSM,
URL: options.URL,
Ref: parentRef,
}
return parentCommit, commitRef
}
Expand All @@ -801,8 +800,7 @@ func makeOSTreePayloadCommit(options *ostree.ImageOptions, defaultRef string) (o
}

return ostree.SourceSpec{
URL: options.URL,
Ref: commitRef,
RHSM: options.RHSM,
URL: options.URL,
Ref: commitRef,
}, nil
}
1 change: 0 additions & 1 deletion pkg/distro/test_distro/distro.go
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,6 @@ func (t *TestImageType) Manifest(b *blueprint.Blueprint, options distro.ImageOpt
}
// copy any other options that might be specified
ostreeSource.URL = options.OSTree.URL
ostreeSource.RHSM = options.OSTree.RHSM
}
ostreeSources = []ostree.SourceSpec{ostreeSource}
}
Expand Down
122 changes: 73 additions & 49 deletions pkg/ostree/ostree.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ import (
"regexp"
"strings"
"time"

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

var (
Expand All @@ -25,9 +23,8 @@ 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
RHSM bool
URL string
Ref string
}

// CommitSpec specifies an ostree commit using any combination of Ref (branch), URL (source), and Checksum (commit ID).
Expand Down Expand Up @@ -68,10 +65,6 @@ type ImageOptions struct {

// If specified, the URL will be used only for metadata.
ContentURL string `json:"contenturl"`

// Indicate if the 'org.osbuild.rhsm.consumer' secret should be added when pulling from the
// remote.
RHSM bool `json:"rhsm"`
}

// Validate the image options. This doesn't verify the existence of any remote
Expand Down Expand Up @@ -141,56 +134,46 @@ func verifyChecksum(commit string) bool {
// ResolveRef 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) {
func ResolveRef(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 150 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 @@ -228,16 +211,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 +236,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 := ResolveRef(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
}
}
7 changes: 2 additions & 5 deletions pkg/ostree/ostree_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,18 +51,15 @@ func TestOstreeResolveRef(t *testing.T) {

type srvConfig struct {
Srv *httptest.Server
RHSM bool
Subs *rhsm.Subscriptions
}
srvConfs := []srvConfig{
{
Srv: srv,
RHSM: false,
Subs: nil,
},
{
Srv: srv2,
RHSM: true,
Subs: subs,
},
}
Expand All @@ -78,7 +75,7 @@ func TestOstreeResolveRef(t *testing.T) {
{srvConf.Srv.URL, "valid/ostree/ref"}: goodRef,
}
for in, expOut := range validCases {
out, err := ResolveRef(in.location, in.ref, srvConf.RHSM, srvConf.Subs, &mTLSSrv.CAPath)
out, err := ResolveRef(in.location, in.ref, mTLSSrv.CAPath, mTLSSrv.ClientCrtPath, mTLSSrv.ClientKeyPath, nil)
assert.NoError(t, err)
assert.Equal(t, expOut, out)
}
Expand All @@ -91,7 +88,7 @@ func TestOstreeResolveRef(t *testing.T) {
{srvConf.Srv.URL, "get_bad_ref"}: fmt.Sprintf("ostree repository \"%s/refs/heads/get_bad_ref\" returned invalid reference", srvConf.Srv.URL),
}
for in, expMsg := range errCases {
_, err := ResolveRef(in.location, in.ref, srvConf.RHSM, srvConf.Subs, &mTLSSrv.CAPath)
_, err := ResolveRef(in.location, in.ref, mTLSSrv.CAPath, mTLSSrv.ClientCrtPath, mTLSSrv.ClientKeyPath, nil)
assert.EqualError(t, err, expMsg)
}
}
Expand Down

0 comments on commit 3b8187d

Please sign in to comment.