rpmmd: add ssl fields to RepoConfig

rpmmd now contains three repo config structs. RepoConfig is for use
throughout osbuild-composer. repository is for reading from our
internal json repository files before creating a RepoConfig.
dnfRepoConfig is is for use within rpmmd and it matches what dnf-json
expects from a repo.

Repos now contain support for rhsm. In order to connect to rhel's
cdn, dnf must pass an sslcacert, sslcakey, and sslclientcert. When a
repo is used for fetching metadata or depsolving it is checked for
rhsm secrets. If secrets are needed they are retrieved from the host
system. Packages requiring rhsm are marked as requiring
"org.osbuild.rhsm" secrets.
This commit is contained in:
Jacob Kozol 2020-05-07 20:41:41 +02:00 committed by Tom Gundersen
parent 8750dc467b
commit d19ffb4eb9

View file

@ -6,22 +6,48 @@ import (
"io/ioutil" "io/ioutil"
"os" "os"
"os/exec" "os/exec"
"path/filepath"
"sort" "sort"
"strings"
"time" "time"
"github.com/gobwas/glob" "github.com/gobwas/glob"
"github.com/google/uuid"
) )
type RepoConfig struct { type repository struct {
Id string `json:"id"`
BaseURL string `json:"baseurl,omitempty"`
Metalink string `json:"metalink,omitempty"`
MirrorList string `json:"mirrorlist,omitempty"`
GPGKey string `json:"gpgkey,omitempty"`
RHSM bool `json:"rhsm,omitempty"`
}
type dnfRepoConfig struct {
Id string `json:"id"` Id string `json:"id"`
BaseURL string `json:"baseurl,omitempty"` BaseURL string `json:"baseurl,omitempty"`
Metalink string `json:"metalink,omitempty"` Metalink string `json:"metalink,omitempty"`
MirrorList string `json:"mirrorlist,omitempty"` MirrorList string `json:"mirrorlist,omitempty"`
GPGKey string `json:"gpgkey,omitempty"` GPGKey string `json:"gpgkey,omitempty"`
IgnoreSSL bool `json:"ignoressl"` IgnoreSSL bool `json:"ignoressl"`
SSLCACert string `json:"sslcacert,omitempty"`
SSLClientKey string `json:"sslclientkey,omitempty"`
SSLClientCert string `json:"sslclientcert,omitempty"`
MetadataExpire string `json:"metadata_expire,omitempty"` MetadataExpire string `json:"metadata_expire,omitempty"`
} }
type RepoConfig struct {
Id string
BaseURL string
Metalink string
MirrorList string
GPGKey string
IgnoreSSL bool
MetadataExpire string
RHSM bool
}
type PackageList []Package type PackageList []Package
type Package struct { type Package struct {
@ -72,6 +98,7 @@ type PackageSpec struct {
Path string `json:"path,omitempty"` Path string `json:"path,omitempty"`
RemoteLocation string `json:"remote_location,omitempty"` RemoteLocation string `json:"remote_location,omitempty"`
Checksum string `json:"checksum,omitempty"` Checksum string `json:"checksum,omitempty"`
Secrets string `json:"secrets,omitempty"`
} }
type PackageSource struct { type PackageSource struct {
@ -125,6 +152,35 @@ func (re *RepositoryError) Error() string {
return re.msg return re.msg
} }
type RHSMSecrets struct {
SSLCACert string `json:"sslcacert,omitempty"`
SSLClientKey string `json:"sslclientkey,omitempty"`
SSLClientCert string `json:"sslclientcert,omitempty"`
}
var rhsmSecrets RHSMSecrets
func getRHSMSecrets() (RHSMSecrets, error) {
if rhsmSecrets == (RHSMSecrets{}) {
keys, err := filepath.Glob("/etc/pki/entitlement/*-key.pem")
if err != nil {
return rhsmSecrets, &RepositoryError{fmt.Sprintf("unable to find client key in /etc/pki/entitlement/: %v", err)}
}
for _, key := range keys {
cert := strings.TrimSuffix(key, "-key.pem") + ".pem"
if _, err := os.Stat(cert); err == nil {
rhsmSecrets = RHSMSecrets{
SSLCACert: "/etc/rhsm/ca/redhat-uep.pem",
SSLClientKey: key,
SSLClientCert: cert,
}
break
}
}
}
return rhsmSecrets, nil
}
func LoadRepositories(confPaths []string, distro string) (map[string][]RepoConfig, error) { func LoadRepositories(confPaths []string, distro string) (map[string][]RepoConfig, error) {
var f *os.File var f *os.File
var err error var err error
@ -143,14 +199,29 @@ func LoadRepositories(confPaths []string, distro string) (map[string][]RepoConfi
} }
defer f.Close() defer f.Close()
var repos map[string][]RepoConfig var reposMap map[string][]repository
repoConfigs := make(map[string][]RepoConfig)
err = json.NewDecoder(f).Decode(&repos) err = json.NewDecoder(f).Decode(&reposMap)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return repos, nil for arch, repos := range reposMap {
for _, repo := range repos {
config := RepoConfig{
Id: repo.Id,
BaseURL: repo.BaseURL,
Metalink: repo.Metalink,
MirrorList: repo.MirrorList,
GPGKey: repo.GPGKey,
RHSM: repo.RHSM,
}
repoConfigs[arch] = append(repoConfigs[arch], config)
}
}
return repoConfigs, nil
} }
func runDNF(command string, arguments interface{}, result interface{}) error { func runDNF(command string, arguments interface{}, result interface{}) error {
@ -223,18 +294,52 @@ func NewRPMMD(cacheDir string) RPMMD {
} }
} }
func (repo RepoConfig) toDNFRepoConfig() (dnfRepoConfig, error) {
id := uuid.New().String()
dnfRepo := dnfRepoConfig{
Id: id,
BaseURL: repo.BaseURL,
Metalink: repo.Metalink,
MirrorList: repo.MirrorList,
GPGKey: repo.GPGKey,
IgnoreSSL: repo.IgnoreSSL,
MetadataExpire: repo.MetadataExpire,
}
if repo.RHSM {
secrets, err := getRHSMSecrets()
if err != nil {
return dnfRepoConfig{}, err
}
dnfRepo.SSLCACert = secrets.SSLCACert
dnfRepo.SSLClientKey = secrets.SSLClientKey
dnfRepo.SSLClientCert = secrets.SSLClientCert
}
return dnfRepo, nil
}
func (r *rpmmdImpl) FetchMetadata(repos []RepoConfig, modulePlatformID string, arch string) (PackageList, map[string]string, error) { func (r *rpmmdImpl) FetchMetadata(repos []RepoConfig, modulePlatformID string, arch string) (PackageList, map[string]string, error) {
var dnfRepoConfigs []dnfRepoConfig
for _, repo := range repos {
dnfRepo, err := repo.toDNFRepoConfig()
if err != nil {
return nil, nil, err
}
dnfRepoConfigs = append(dnfRepoConfigs, dnfRepo)
}
var arguments = struct { var arguments = struct {
Repos []RepoConfig `json:"repos"` Repos []dnfRepoConfig `json:"repos"`
CacheDir string `json:"cachedir"` CacheDir string `json:"cachedir"`
ModulePlatformID string `json:"module_platform_id"` ModulePlatformID string `json:"module_platform_id"`
Arch string `json:"arch"` Arch string `json:"arch"`
}{repos, r.CacheDir, modulePlatformID, arch} }{dnfRepoConfigs, r.CacheDir, modulePlatformID, arch}
var reply struct { var reply struct {
Checksums map[string]string `json:"checksums"` Checksums map[string]string `json:"checksums"`
Packages PackageList `json:"packages"` Packages PackageList `json:"packages"`
} }
err := runDNF("dump", arguments, &reply) err := runDNF("dump", arguments, &reply)
sort.Slice(reply.Packages, func(i, j int) bool { sort.Slice(reply.Packages, func(i, j int) bool {
return reply.Packages[i].Name < reply.Packages[j].Name return reply.Packages[i].Name < reply.Packages[j].Name
}) })
@ -242,19 +347,41 @@ func (r *rpmmdImpl) FetchMetadata(repos []RepoConfig, modulePlatformID string, a
} }
func (r *rpmmdImpl) Depsolve(specs, excludeSpecs []string, repos []RepoConfig, modulePlatformID, arch string) ([]PackageSpec, map[string]string, error) { func (r *rpmmdImpl) Depsolve(specs, excludeSpecs []string, repos []RepoConfig, modulePlatformID, arch string) ([]PackageSpec, map[string]string, error) {
repoMap := make(map[string]RepoConfig)
var dnfRepoConfigs []dnfRepoConfig
for _, repo := range repos {
id := repo.Id
repoMap[id] = repo
dnfRepo, err := repo.toDNFRepoConfig()
if err != nil {
return nil, nil, err
}
dnfRepoConfigs = append(dnfRepoConfigs, dnfRepo)
}
var arguments = struct { var arguments = struct {
PackageSpecs []string `json:"package-specs"` PackageSpecs []string `json:"package-specs"`
ExcludSpecs []string `json:"exclude-specs"` ExcludSpecs []string `json:"exclude-specs"`
Repos []RepoConfig `json:"repos"` Repos []dnfRepoConfig `json:"repos"`
CacheDir string `json:"cachedir"` CacheDir string `json:"cachedir"`
ModulePlatformID string `json:"module_platform_id"` ModulePlatformID string `json:"module_platform_id"`
Arch string `json:"arch"` Arch string `json:"arch"`
}{specs, excludeSpecs, repos, r.CacheDir, modulePlatformID, arch} }{specs, excludeSpecs, dnfRepoConfigs, r.CacheDir, modulePlatformID, arch}
var reply struct { var reply struct {
Checksums map[string]string `json:"checksums"` Checksums map[string]string `json:"checksums"`
Dependencies []PackageSpec `json:"dependencies"` Dependencies []PackageSpec `json:"dependencies"`
} }
err := runDNF("depsolve", arguments, &reply) err := runDNF("depsolve", arguments, &reply)
for i, pack := range reply.Dependencies {
id := pack.RepoID
if repoMap[id].RHSM {
reply.Dependencies[i].Secrets = "org.osbuild.rhsm"
}
}
return reply.Dependencies, reply.Checksums, err return reply.Dependencies, reply.Checksums, err
} }