Introduce RBAC support & deckhand manifests fetching

Small other improvements included.

Change-Id: Ibcf3fc2f5383a4b1faacff814d492d13d2a5a8e5
Signed-off-by: Ruslan Aliev <raliev@mirantis.com>
This commit is contained in:
Ruslan Aliev 2024-03-05 16:18:35 -06:00
parent 7094ac6ace
commit 6a5ac89168
5 changed files with 169 additions and 21 deletions

3
go.mod
View File

@ -3,10 +3,12 @@ module opendev.org/airship/armada-go
go 1.20
require (
github.com/databus23/goslo.policy v0.0.0-20210929125152-81bf2876dbdb
github.com/gin-gonic/gin v1.9.1
github.com/spf13/cobra v1.7.0
github.com/spf13/viper v1.18.2
golang.org/x/sync v0.5.0
gopkg.in/yaml.v3 v3.0.1
k8s.io/api v0.28.4
k8s.io/apiextensions-apiserver v0.28.3
k8s.io/apimachinery v0.28.4
@ -80,7 +82,6 @@ require (
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 // indirect
k8s.io/utils v0.0.0-20230505201702-9f6742963106 // indirect
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect

2
go.sum
View File

@ -8,6 +8,8 @@ github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhD
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/databus23/goslo.policy v0.0.0-20210929125152-81bf2876dbdb h1:8JB2G8t3o1iCL8vCzssUj2Nn2qjqSab2/G3xXhvkpPQ=
github.com/databus23/goslo.policy v0.0.0-20210929125152-81bf2876dbdb/go.mod h1:tRj172JgwQmUmEqZZJBWzYWFStitMFTtb95NtUnmpkw=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=

View File

@ -21,7 +21,11 @@ import (
"flag"
"fmt"
"io"
"net/http"
"net/url"
"opendev.org/airship/armada-go/pkg/auth"
"os"
"regexp"
"strings"
"time"
@ -326,10 +330,39 @@ func (c *RunCommand) ValidateManifests() error {
func (c *RunCommand) ParseManifests() error {
klog.V(5).Infof("parsing manifests started, path: %s", c.Manifests)
f, err := os.Open(c.Manifests)
var f io.ReadCloser
u, err := url.Parse(c.Manifests)
if err != nil {
return err
}
if u.Scheme == "" {
f, err = os.Open(c.Manifests)
if err != nil {
return err
}
} else if u.Scheme == "deckhand+http" {
reg, err := regexp.Compile("^[^+]+\\+")
if err != nil {
return err
}
deckhandUrl := reg.ReplaceAllString(c.Manifests, "")
req, err := http.NewRequest("GET", deckhandUrl, nil)
if err != nil {
return err
}
token, err := auth.Authenticate()
if err != nil {
return err
}
req.Header.Set("X-Auth-Token", token)
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return err
}
f = resp.Body
}
defer f.Close()
c.airCharts = map[string]*AirshipChart{}

View File

@ -1,9 +1,11 @@
package server
package auth
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"github.com/spf13/viper"
"net/http"
"strings"
"time"
@ -13,7 +15,7 @@ import (
"opendev.org/airship/armada-go/pkg/log"
)
var Log func(string, ...interface{}) = func(format string, a ...interface{}) {
var Log = func(format string, a ...interface{}) {
log.Printf(format, a...)
}
@ -287,3 +289,56 @@ func filterIncomingHeaders(req *http.Request) {
req.Header.Del("X-User")
req.Header.Del("X-Role")
}
func Authenticate() (string, error) {
authUrl := viper.Sub("keystone_authtoken").GetString("auth_url")
username := viper.Sub("keystone_authtoken").GetString("username")
password := viper.Sub("keystone_authtoken").GetString("password")
projectDomainName := viper.Sub("keystone_authtoken").GetString("project_domain_name")
projectName := viper.Sub("keystone_authtoken").GetString("project_name")
userDomainName := viper.Sub("keystone_authtoken").GetString("user_domain_name")
jsonData := []byte(fmt.Sprintf(`{
"auth": {
"identity": {
"methods": ["password"],
"password": {
"user": {
"name": "%s",
"domain": { "id": "%s" },
"password": "%s"
}
}
},
"scope": {
"project": {
"name": "%s",
"domain": { "id": "%s" }
}
}
}
}`, username, userDomainName, password, projectName, projectDomainName))
req, err := http.NewRequest("POST", authUrl+"/auth/tokens", bytes.NewBuffer(jsonData))
if err != nil {
return "", err
}
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
if resp.StatusCode != 201 {
return "", errors.New("http: not authorized")
}
token := resp.Header.Get("X-Subject-Token")
if token == "" {
return "", errors.New("http: keystone token is empty")
}
return token, nil
}

View File

@ -15,13 +15,18 @@
package server
import (
"net/http"
"fmt"
policy "github.com/databus23/goslo.policy"
"github.com/gin-gonic/gin"
"github.com/spf13/viper"
"gopkg.in/yaml.v3"
"net/http"
"opendev.org/airship/armada-go/pkg/apply"
auth2 "opendev.org/airship/armada-go/pkg/auth"
"opendev.org/airship/armada-go/pkg/config"
"opendev.org/airship/armada-go/pkg/log"
"os"
"strings"
)
// RunCommand phase run command
@ -29,17 +34,53 @@ type RunCommand struct {
Factory config.Factory
}
type JsonDataRequest struct {
Href string `json:"hrefs" binding:"required"`
Overrides []any `json:"overrides"`
}
func PolicyEnforcer(enforcer *policy.Enforcer, rule string) gin.HandlerFunc {
return func(context *gin.Context) {
ctx := policy.Context{
Roles: strings.Split(context.GetHeader("X-Roles"), ","),
Logger: log.Printf,
}
if enforcer.Enforce(rule, ctx) {
context.Next()
} else {
context.String(401, "oslo policy error")
}
}
}
func Apply(c *gin.Context) {
if c.GetHeader("X-Identity-Status") == "Confirmed" {
c.JSON(200, gin.H{
"message": gin.H{
"install": []any{},
"upgrade": []any{},
"diff": []any{},
"purge": []any{},
"protected": []any{},
},
})
if c.ContentType() == "application/json" {
targetManifest := c.Query("target_manifest")
var dataReq JsonDataRequest
if err := c.BindJSON(&dataReq); err != nil {
c.String(500, "internal error", err.Error())
return
}
runOpts := apply.RunCommand{Manifests: dataReq.Href, TargetManifest: targetManifest, Out: os.Stdout}
if err := runOpts.RunE(); err != nil {
c.String(500, "apply error", err.Error())
return
}
c.JSON(200, gin.H{
"message": gin.H{
"install": []any{},
"upgrade": []any{},
"diff": []any{},
"purge": []any{},
"protected": []any{},
},
})
} else {
c.Status(500)
}
} else {
c.Status(401)
}
@ -66,7 +107,7 @@ func Releases(c *gin.Context) {
if c.GetHeader("X-Identity-Status") == "Confirmed" {
c.JSON(200, gin.H{
"releases": gin.H{
"ucp": []string{"clcp-ucp-armada"},
"ucp": []string{},
},
})
} else {
@ -88,11 +129,27 @@ func (c *RunCommand) RunE() error {
log.Printf("armada-go server has been started")
r := gin.Default()
r.Use(gin.Logger())
auth := New(viper.Sub("keystone_authtoken").GetString("auth_url"))
auth := auth2.New(viper.Sub("keystone_authtoken").GetString("auth_url"))
r.POST("/api/v1.0/apply", auth.Handler(r.Handler()), Apply)
r.POST("/api/v1.0/validatedesign", auth.Handler(r.Handler()), Validate)
r.GET("/api/v1.0/releases", auth.Handler(r.Handler()), Releases)
buf, err := os.ReadFile("/etc/armada/policy.yaml")
if err != nil {
return err
}
var pol map[string]string
err = yaml.Unmarshal(buf, &pol)
if err != nil {
return fmt.Errorf("in file %q: %w", "policy", err)
}
enf, err := policy.NewEnforcer(pol)
if err != nil {
return err
}
r.POST("/api/v1.0/apply", auth.Handler(r.Handler()), PolicyEnforcer(enf, "armada:create_endpoints"), Apply)
r.POST("/api/v1.0/validatedesign", auth.Handler(r.Handler()), PolicyEnforcer(enf, "armada:validate_manifest"), Validate)
r.GET("/api/v1.0/releases", auth.Handler(r.Handler()), PolicyEnforcer(enf, "armada:get_release"), Releases)
r.GET("/api/v1.0/health", Health)
return r.Run(":8000")
}