cloudapi: Add x-rh-identity header filter
This commit is contained in:
parent
2a42d05a10
commit
19db3ff1d4
4 changed files with 112 additions and 15 deletions
|
|
@ -111,17 +111,21 @@ func (c *Composer) InitAPI(cert, key string, l net.Listener) error {
|
|||
c.api = cloudapi.NewServer(c.workers, c.rpm, c.distros)
|
||||
c.koji = kojiapi.NewServer(c.logger, c.workers, c.rpm, c.distros)
|
||||
|
||||
tlsConfig, err := createTLSConfig(&connectionConfig{
|
||||
CACertFile: c.config.Koji.CA,
|
||||
ServerKeyFile: key,
|
||||
ServerCertFile: cert,
|
||||
AllowedDomains: c.config.Koji.AllowedDomains,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error creating TLS configuration: %v", err)
|
||||
}
|
||||
if len(c.config.ComposerAPI.IdentityFilter) > 0 {
|
||||
c.apiListener = l
|
||||
} else {
|
||||
tlsConfig, err := createTLSConfig(&connectionConfig{
|
||||
CACertFile: c.config.Koji.CA,
|
||||
ServerKeyFile: key,
|
||||
ServerCertFile: cert,
|
||||
AllowedDomains: c.config.Koji.AllowedDomains,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error creating TLS configuration: %v", err)
|
||||
}
|
||||
|
||||
c.apiListener = tls.NewListener(l, tlsConfig)
|
||||
c.apiListener = tls.NewListener(l, tlsConfig)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -195,7 +199,7 @@ func (c *Composer) Start() error {
|
|||
// Add a "/" here, because http.ServeMux expects the
|
||||
// trailing slash for rooted subtrees, whereas the
|
||||
// handler functions don't.
|
||||
mux.Handle(apiRoute+"/", c.api.Handler(apiRoute))
|
||||
mux.Handle(apiRoute+"/", c.api.Handler(apiRoute, c.config.ComposerAPI.IdentityFilter))
|
||||
mux.Handle(kojiRoute+"/", c.koji.Handler(kojiRoute))
|
||||
|
||||
s := &http.Server{
|
||||
|
|
|
|||
|
|
@ -15,6 +15,9 @@ type ComposerConfigFile struct {
|
|||
AllowedDomains []string `toml:"allowed_domains"`
|
||||
CA string `toml:"ca"`
|
||||
} `toml:"worker"`
|
||||
ComposerAPI struct {
|
||||
IdentityFilter []string `toml:"identity_filter"`
|
||||
} `toml:"composer_api"`
|
||||
}
|
||||
|
||||
func LoadConfig(name string) (*ComposerConfigFile, error) {
|
||||
|
|
|
|||
|
|
@ -3,12 +3,15 @@
|
|||
package cloudapi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math"
|
||||
"math/big"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/google/uuid"
|
||||
|
|
@ -23,9 +26,22 @@ import (
|
|||
|
||||
// Server represents the state of the cloud Server
|
||||
type Server struct {
|
||||
workers *worker.Server
|
||||
rpmMetadata rpmmd.RPMMD
|
||||
distros *distroregistry.Registry
|
||||
workers *worker.Server
|
||||
rpmMetadata rpmmd.RPMMD
|
||||
distros *distroregistry.Registry
|
||||
identityFilter []string
|
||||
}
|
||||
|
||||
type contextKey int
|
||||
|
||||
const (
|
||||
identityHeaderKey contextKey = iota
|
||||
)
|
||||
|
||||
type IdentityHeader struct {
|
||||
Identity struct {
|
||||
AccountNumber string `json:"account_number"`
|
||||
} `json:"identity"`
|
||||
}
|
||||
|
||||
// NewServer creates a new cloud server
|
||||
|
|
@ -40,9 +56,13 @@ func NewServer(workers *worker.Server, rpmMetadata rpmmd.RPMMD, distros *distror
|
|||
|
||||
// Create an http.Handler() for this server, that provides the composer API at
|
||||
// the given path.
|
||||
func (server *Server) Handler(path string) http.Handler {
|
||||
func (server *Server) Handler(path string, identityFilter []string) http.Handler {
|
||||
r := chi.NewRouter()
|
||||
|
||||
if len(identityFilter) > 0 {
|
||||
server.identityFilter = identityFilter
|
||||
r.Use(server.VerifyIdentityHeader)
|
||||
}
|
||||
r.Route(path, func(r chi.Router) {
|
||||
HandlerFromMux(server, r)
|
||||
})
|
||||
|
|
@ -50,6 +70,37 @@ func (server *Server) Handler(path string) http.Handler {
|
|||
return r
|
||||
}
|
||||
|
||||
func (server *Server) VerifyIdentityHeader(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
idHeaderB64 := r.Header["X-Rh-Identity"]
|
||||
if len(idHeaderB64) != 1 {
|
||||
http.Error(w, "Auth header is not present", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
b64Result, err := base64.StdEncoding.DecodeString(idHeaderB64[0])
|
||||
if err != nil {
|
||||
http.Error(w, "Auth header has incorrect format", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
var idHeader IdentityHeader
|
||||
err = json.Unmarshal([]byte(strings.TrimSuffix(fmt.Sprintf("%s", b64Result), "\n")), &idHeader)
|
||||
if err != nil {
|
||||
http.Error(w, "Auth header has incorrect format", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
for _, i := range server.identityFilter {
|
||||
if idHeader.Identity.AccountNumber == i {
|
||||
next.ServeHTTP(w, r.WithContext(context.WithValue(r.Context(), identityHeaderKey, idHeader)))
|
||||
return
|
||||
}
|
||||
}
|
||||
http.Error(w, "Account not allowed", http.StatusNotFound)
|
||||
})
|
||||
}
|
||||
|
||||
// Compose handles a new /compose POST request
|
||||
func (server *Server) Compose(w http.ResponseWriter, r *http.Request) {
|
||||
contentType := r.Header["Content-Type"]
|
||||
|
|
|
|||
|
|
@ -658,4 +658,43 @@ case $CLOUD_PROVIDER in
|
|||
;;
|
||||
esac
|
||||
|
||||
|
||||
|
||||
# Verify the identityfilter
|
||||
cat <<EOF | sudo tee "/etc/osbuild-composer/osbuild-composer.toml"
|
||||
[koji]
|
||||
allowed_domains = [ "localhost", "client.osbuild.org" ]
|
||||
ca = "/etc/osbuild-composer/ca-crt.pem"
|
||||
|
||||
[worker]
|
||||
allowed_domains = [ "localhost", "worker.osbuild.org" ]
|
||||
ca = "/etc/osbuild-composer/ca-crt.pem"
|
||||
|
||||
[composer_api]
|
||||
identity_filter = ["000000"]
|
||||
EOF
|
||||
|
||||
sudo systemctl restart osbuild-composer
|
||||
|
||||
# account number 000000
|
||||
VALIDAUTHSTRING="eyJlbnRpdGxlbWVudHMiOnsiaW5zaWdodHMiOnsiaXNfZW50aXRsZWQiOnRydWV9LCJzbWFydF9tYW5hZ2VtZW50Ijp7ImlzX2VudGl0bGVkIjp0cnVlfSwib3BlbnNoaWZ0Ijp7ImlzX2VudGl0bGVkIjp0cnVlfSwiaHlicmlkIjp7ImlzX2VudGl0bGVkIjp0cnVlfSwibWlncmF0aW9ucyI6eyJpc19lbnRpdGxlZCI6dHJ1ZX0sImFuc2libGUiOnsiaXNfZW50aXRsZWQiOnRydWV9fSwiaWRlbnRpdHkiOnsiYWNjb3VudF9udW1iZXIiOiIwMDAwMDAiLCJ0eXBlIjoiVXNlciIsInVzZXIiOnsidXNlcm5hbWUiOiJ1c2VyIiwiZW1haWwiOiJ1c2VyQHVzZXIudXNlciIsImZpcnN0X25hbWUiOiJ1c2VyIiwibGFzdF9uYW1lIjoidXNlciIsImlzX2FjdGl2ZSI6dHJ1ZSwiaXNfb3JnX2FkbWluIjp0cnVlLCJpc19pbnRlcm5hbCI6dHJ1ZSwibG9jYWxlIjoiZW4tVVMifSwiaW50ZXJuYWwiOnsib3JnX2lkIjoiMDAwMDAwIn19fQ=="
|
||||
# account number 000001
|
||||
INVALIDAUTHSTRING="eyJlbnRpdGxlbWVudHMiOnsiaW5zaWdodHMiOnsiaXNfZW50aXRsZWQiOnRydWV9LCJzbWFydF9tYW5hZ2VtZW50Ijp7ImlzX2VudGl0bGVkIjp0cnVlfSwib3BlbnNoaWZ0Ijp7ImlzX2VudGl0bGVkIjp0cnVlfSwiaHlicmlkIjp7ImlzX2VudGl0bGVkIjp0cnVlfSwibWlncmF0aW9ucyI6eyJpc19lbnRpdGxlZCI6dHJ1ZX0sImFuc2libGUiOnsiaXNfZW50aXRsZWQiOnRydWV9fSwiaWRlbnRpdHkiOnsiYWNjb3VudF9udW1iZXIiOiIwMDAwMDMiLCJ0eXBlIjoiVXNlciIsInVzZXIiOnsidXNlcm5hbWUiOiJ1c2VyIiwiZW1haWwiOiJ1c2VyQHVzZXIudXNlciIsImZpcnN0X25hbWUiOiJ1c2VyIiwibGFzdF9uYW1lIjoidXNlciIsImlzX2FjdGl2ZSI6dHJ1ZSwiaXNfb3JnX2FkbWluIjp0cnVlLCJpc19pbnRlcm5hbCI6dHJ1ZSwibG9jYWxlIjoiZW4tVVMifSwiaW50ZXJuYWwiOnsib3JnX2lkIjoiMDAwMDAwIn19fQo="
|
||||
|
||||
curl \
|
||||
--silent \
|
||||
--show-error \
|
||||
--header "x-rh-identity: $VALIDAUTHSTRING" \
|
||||
http://localhost:443/api/composer/v1/version | jq .
|
||||
|
||||
#
|
||||
# Make sure the invalid auth string returns a 404
|
||||
#
|
||||
[ "$(curl \
|
||||
--silent \
|
||||
--output /dev/null \
|
||||
--write-out '%{http_code}' \
|
||||
--header "x-rh-identity: $INVALIDAUTHSTRING" \
|
||||
http://localhost:443/api/composer/v1/version)" = "404" ]
|
||||
|
||||
exit 0
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue