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"
"os"
"os/exec"
"path/filepath"
"sort"
"strings"
"time"
"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"`
BaseURL string `json:"baseurl,omitempty"`
Metalink string `json:"metalink,omitempty"`
MirrorList string `json:"mirrorlist,omitempty"`
GPGKey string `json:"gpgkey,omitempty"`
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"`
}
type RepoConfig struct {
Id string
BaseURL string
Metalink string
MirrorList string
GPGKey string
IgnoreSSL bool
MetadataExpire string
RHSM bool
}
type PackageList []Package
type Package struct {
@ -72,6 +98,7 @@ type PackageSpec struct {
Path string `json:"path,omitempty"`
RemoteLocation string `json:"remote_location,omitempty"`
Checksum string `json:"checksum,omitempty"`
Secrets string `json:"secrets,omitempty"`
}
type PackageSource struct {
@ -125,6 +152,35 @@ func (re *RepositoryError) Error() string {
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) {
var f *os.File
var err error
@ -143,14 +199,29 @@ func LoadRepositories(confPaths []string, distro string) (map[string][]RepoConfi
}
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 {
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 {
@ -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) {
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 {
Repos []RepoConfig `json:"repos"`
CacheDir string `json:"cachedir"`
ModulePlatformID string `json:"module_platform_id"`
Arch string `json:"arch"`
}{repos, r.CacheDir, modulePlatformID, arch}
Repos []dnfRepoConfig `json:"repos"`
CacheDir string `json:"cachedir"`
ModulePlatformID string `json:"module_platform_id"`
Arch string `json:"arch"`
}{dnfRepoConfigs, r.CacheDir, modulePlatformID, arch}
var reply struct {
Checksums map[string]string `json:"checksums"`
Packages PackageList `json:"packages"`
}
err := runDNF("dump", arguments, &reply)
sort.Slice(reply.Packages, func(i, j int) bool {
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) {
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 {
PackageSpecs []string `json:"package-specs"`
ExcludSpecs []string `json:"exclude-specs"`
Repos []RepoConfig `json:"repos"`
CacheDir string `json:"cachedir"`
ModulePlatformID string `json:"module_platform_id"`
Arch string `json:"arch"`
}{specs, excludeSpecs, repos, r.CacheDir, modulePlatformID, arch}
PackageSpecs []string `json:"package-specs"`
ExcludSpecs []string `json:"exclude-specs"`
Repos []dnfRepoConfig `json:"repos"`
CacheDir string `json:"cachedir"`
ModulePlatformID string `json:"module_platform_id"`
Arch string `json:"arch"`
}{specs, excludeSpecs, dnfRepoConfigs, r.CacheDir, modulePlatformID, arch}
var reply struct {
Checksums map[string]string `json:"checksums"`
Dependencies []PackageSpec `json:"dependencies"`
}
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
}