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:
parent
7094ac6ace
commit
6a5ac89168
3
go.mod
3
go.mod
@ -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
2
go.sum
@ -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=
|
||||
|
@ -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{}
|
||||
|
@ -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
|
||||
}
|
@ -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")
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user