first compiling version
This commit is contained in:
parent
60fb40d61a
commit
3d54199faa
27 changed files with 908 additions and 243 deletions
|
|
@ -5,7 +5,6 @@ package client
|
|||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
|
@ -13,19 +12,15 @@ import (
|
|||
"net/http"
|
||||
"net/http/httputil"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
endpoint string
|
||||
username string
|
||||
password string
|
||||
auth bool
|
||||
httpClient *http.Client
|
||||
debugWriter io.Writer
|
||||
insecure bool
|
||||
//insecure bool
|
||||
}
|
||||
|
||||
var (
|
||||
|
|
@ -52,36 +47,21 @@ func WithHTTPClient(httpClient *http.Client) ClientOption {
|
|||
}
|
||||
}
|
||||
|
||||
func WithInsecureClient(insecure bool) ClientOption {
|
||||
return func(client *Client) {
|
||||
client.insecure = insecure
|
||||
}
|
||||
}
|
||||
|
||||
func WithCredentials(username, password string) ClientOption {
|
||||
return func(client *Client) {
|
||||
client.username = username
|
||||
client.password = password
|
||||
client.auth = true
|
||||
}
|
||||
}
|
||||
//func WithInsecureClient(insecure bool) ClientOption {
|
||||
// return func(client *Client) {
|
||||
// client.insecure = insecure
|
||||
// }
|
||||
//}
|
||||
|
||||
var (
|
||||
ErrMissingEndpointEnv = errors.New("missing environment variable HAPROXY_LB_ENDPOINT")
|
||||
ErrMissingUserEnv = errors.New("missing environment variable HAPROXY_LB_USER")
|
||||
ErrMissingPasswordEnv = errors.New("missing environment variable HAPROXY_LB_PASSWORD")
|
||||
ErrMissingEndpointEnv = errors.New("missing environment variable KEYCTL_ENDPOINT")
|
||||
|
||||
ErrMissingEndpoint = errors.New("missing Endpoint in config")
|
||||
ErrMissingUser = errors.New("missing User in config")
|
||||
ErrMissingPassword = errors.New("missing Password in config")
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Endpoint string `json:"endpoint"`
|
||||
NoAuth bool `json:"no_auth"`
|
||||
User string `json:"user"`
|
||||
Password string `json:"password"`
|
||||
Insecure bool `json:"insecure"`
|
||||
//Insecure bool `json:"insecure"`
|
||||
}
|
||||
|
||||
func ClientOptionsFromConfig(cfg Config) (options []ClientOption, err error) {
|
||||
|
|
@ -91,53 +71,23 @@ func ClientOptionsFromConfig(cfg Config) (options []ClientOption, err error) {
|
|||
}
|
||||
options = []ClientOption{
|
||||
WithEndpoint(cfg.Endpoint),
|
||||
WithInsecureClient(cfg.Insecure),
|
||||
//WithInsecureClient(cfg.Insecure),
|
||||
}
|
||||
if cfg.NoAuth {
|
||||
return
|
||||
}
|
||||
if cfg.User == "" {
|
||||
err = ErrMissingUser
|
||||
return
|
||||
}
|
||||
if cfg.Password == "" {
|
||||
err = ErrMissingPassword
|
||||
return
|
||||
}
|
||||
options = append(options, WithCredentials(cfg.User, cfg.Password))
|
||||
return
|
||||
}
|
||||
|
||||
func ClientOptionsFromEnv() (options []ClientOption, err error) {
|
||||
endpoint := os.Getenv("HAPROXY_LB_ENDPOINT")
|
||||
endpoint := os.Getenv("KEYCTL_ENDPOINT")
|
||||
if endpoint == "" {
|
||||
err = ErrMissingEndpointEnv
|
||||
return
|
||||
}
|
||||
insecure, _ := strconv.ParseBool(os.Getenv("HAPROXY_LB_INSECURE"))
|
||||
//insecure, _ := strconv.ParseBool(os.Getenv("HAPROXY_LB_INSECURE"))
|
||||
|
||||
options = []ClientOption{
|
||||
WithEndpoint(endpoint),
|
||||
WithInsecureClient(insecure),
|
||||
//WithInsecureClient(insecure),
|
||||
}
|
||||
noAuth, _ := strconv.ParseBool(os.Getenv("HAPROXY_LB_NO_AUTH"))
|
||||
if noAuth {
|
||||
return
|
||||
}
|
||||
|
||||
user := os.Getenv("HAPROXY_LB_USER")
|
||||
if user == "" {
|
||||
err = ErrMissingUserEnv
|
||||
return
|
||||
}
|
||||
|
||||
pass := os.Getenv("HAPROXY_LB_PASSWORD")
|
||||
if pass == "" {
|
||||
err = ErrMissingPasswordEnv
|
||||
return
|
||||
}
|
||||
|
||||
options = append(options, WithCredentials(user, pass))
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -149,20 +99,25 @@ func NewClient(options ...ClientOption) *Client {
|
|||
}
|
||||
|
||||
if client.httpClient == nil {
|
||||
client.httpClient = &http.Client{Transport: &http.Transport{
|
||||
DialContext: (&net.Dialer{
|
||||
Timeout: 30 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
}).DialContext,
|
||||
ForceAttemptHTTP2: true,
|
||||
MaxIdleConns: 100,
|
||||
IdleConnTimeout: 90 * time.Second,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
ExpectContinueTimeout: 1 * time.Second,
|
||||
TLSClientConfig: &tls.Config{
|
||||
InsecureSkipVerify: client.insecure,
|
||||
client.httpClient = &http.Client{
|
||||
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
||||
return http.ErrUseLastResponse
|
||||
},
|
||||
}}
|
||||
Transport: &http.Transport{
|
||||
DialContext: (&net.Dialer{
|
||||
Timeout: 30 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
}).DialContext,
|
||||
//ForceAttemptHTTP2: true,
|
||||
MaxIdleConns: 100,
|
||||
IdleConnTimeout: 90 * time.Second,
|
||||
//TLSHandshakeTimeout: 10 * time.Second,
|
||||
ExpectContinueTimeout: 1 * time.Second,
|
||||
//TLSClientConfig: &tls.Config{
|
||||
// InsecureSkipVerify: client.insecure,
|
||||
//},
|
||||
},
|
||||
}
|
||||
}
|
||||
return client
|
||||
}
|
||||
|
|
@ -173,14 +128,11 @@ func (c *Client) NewRequest(ctx context.Context, method, path string, body io.Re
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if c.auth {
|
||||
req.SetBasicAuth(c.username, c.password)
|
||||
}
|
||||
req = req.WithContext(ctx)
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func (c *Client) Do(r *http.Request, v interface{}) (resp *http.Response, err error) {
|
||||
func (c *Client) Do(r *http.Request, v any) (resp *http.Response, err error) {
|
||||
if c.debugWriter != nil {
|
||||
dumpReq, err := httputil.DumpRequestOut(r, true)
|
||||
if err != nil {
|
||||
|
|
@ -211,3 +163,19 @@ func (c *Client) Do(r *http.Request, v interface{}) (resp *http.Response, err er
|
|||
err = decodeResp(resp, v)
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Client) FollowRedirect(r *http.Request, v any, delay time.Duration) (resp *http.Response, err error) {
|
||||
rr := r
|
||||
for {
|
||||
resp, err = c.Do(rr, v)
|
||||
if !errors.Is(ErrRedirect, err) {
|
||||
return
|
||||
}
|
||||
rdr := err.(Redirect)
|
||||
rr, err = c.NewRequest(r.Context(), "GET", rdr.Location, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
time.Sleep(delay)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ import (
|
|||
)
|
||||
|
||||
type Error struct {
|
||||
Err string `json:"error"`
|
||||
Status int `json:"status"`
|
||||
Err string `json:"error"`
|
||||
Code int `json:"code"`
|
||||
}
|
||||
|
||||
func (e Error) Error() string {
|
||||
|
|
@ -20,7 +20,18 @@ func (e Error) Is(target error) bool {
|
|||
return e.Err == target.Error()
|
||||
}
|
||||
|
||||
func decodeResp(resp *http.Response, v interface{}) error {
|
||||
type Redirect struct {
|
||||
Location string
|
||||
Code int
|
||||
}
|
||||
|
||||
func (r Redirect) Error() string {
|
||||
return "redirect: " + r.Location
|
||||
}
|
||||
|
||||
var ErrRedirect = Redirect{}
|
||||
|
||||
func decodeResp(resp *http.Response, v any) error {
|
||||
if resp.Body == nil {
|
||||
return nil
|
||||
}
|
||||
|
|
@ -31,12 +42,16 @@ func decodeResp(resp *http.Response, v interface{}) error {
|
|||
}
|
||||
return json.NewDecoder(resp.Body).Decode(&v)
|
||||
}
|
||||
if resp.StatusCode >= 300 && resp.StatusCode < 400 {
|
||||
loc := resp.Header.Get("Location")
|
||||
return Redirect{loc, resp.StatusCode}
|
||||
}
|
||||
var e Error
|
||||
err := json.NewDecoder(resp.Body).Decode(&e)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.StatusCode >= 400 && resp.StatusCode <= 599 {
|
||||
if resp.StatusCode >= 400 && resp.StatusCode < 600 {
|
||||
return e
|
||||
}
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -24,13 +24,7 @@ type Server struct {
|
|||
|
||||
Data *types.ContextData
|
||||
|
||||
init chan struct{}
|
||||
stop chan struct{}
|
||||
stopKeyReset chan struct{}
|
||||
|
||||
listen net.Listener
|
||||
|
||||
db *db.DB
|
||||
}
|
||||
|
||||
type notFoundHandler struct {
|
||||
|
|
@ -46,11 +40,7 @@ func NewServer(log logr.Logger, listen string) (*Server, error) {
|
|||
s := &Server{
|
||||
mux: mux.NewRouter(),
|
||||
log: log,
|
||||
Data: &types.ContextData{},
|
||||
|
||||
init: make(chan struct{}),
|
||||
stop: make(chan struct{}),
|
||||
stopKeyReset: make(chan struct{}),
|
||||
Data: &types.ContextData{Approver: types.NewApprover()},
|
||||
}
|
||||
l, err := net.Listen("tcp", listen)
|
||||
if err != nil {
|
||||
|
|
@ -62,18 +52,17 @@ func NewServer(log logr.Logger, listen string) (*Server, error) {
|
|||
for _, v := range serverv1.Routes {
|
||||
s.mux.HandleFunc(v.Path, s.contextWrapper(v.Handler)).Methods(v.Methods...)
|
||||
}
|
||||
s.start()
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (s *Server) start() {
|
||||
func (s *Server) Start(db *db.DB) {
|
||||
s.srv = &http.Server{
|
||||
Handler: s.mux,
|
||||
ReadTimeout: 10 * time.Second,
|
||||
WriteTimeout: 10 * time.Second,
|
||||
}
|
||||
s.Data.DB = db
|
||||
go func() {
|
||||
<-s.init
|
||||
err := s.srv.Serve(s.listen)
|
||||
if err != nil {
|
||||
s.log.Error(err, "")
|
||||
|
|
@ -89,10 +78,8 @@ func (s *Server) contextWrapper(h types.CtxHandler) http.HandlerFunc {
|
|||
}
|
||||
|
||||
// Stop stops listening for incoming connections and closes currently open connections
|
||||
func (s *Server) Stop() {
|
||||
func (s *Server) Shutdown(ctx context.Context) {
|
||||
s.log.Info("stopping")
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||
defer cancel()
|
||||
err := s.srv.Shutdown(ctx)
|
||||
if err != nil {
|
||||
s.log.Error(err, "")
|
||||
|
|
|
|||
|
|
@ -58,13 +58,19 @@ func (a *Approver) Status(id, aid string) (s Status) {
|
|||
s = app.Status
|
||||
}
|
||||
a.mu.RUnlock()
|
||||
if s == Rejected {
|
||||
a.mu.Lock()
|
||||
delete(a.m, aid)
|
||||
a.mu.Unlock()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (a *Approver) Update(aid string, s Status) {
|
||||
a.mu.Lock()
|
||||
if _, ok := a.m[aid]; ok {
|
||||
a.m[aid].Status = s
|
||||
if app, ok := a.m[aid]; ok {
|
||||
app.Status = s
|
||||
a.m[aid] = app
|
||||
}
|
||||
a.mu.Unlock()
|
||||
}
|
||||
|
|
@ -95,9 +101,3 @@ func (s Status) String() string {
|
|||
}
|
||||
return "[invalid]"
|
||||
}
|
||||
|
||||
type Route struct {
|
||||
Path string
|
||||
Handler CtxHandler
|
||||
Methods []string
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,8 @@ import (
|
|||
)
|
||||
|
||||
type ContextData struct {
|
||||
DB *db.DB
|
||||
DB *db.DB
|
||||
Approver *Approver
|
||||
}
|
||||
|
||||
func NewContext(w http.ResponseWriter, r *http.Request, data *ContextData, log logr.Logger) *Context {
|
||||
|
|
|
|||
|
|
@ -11,13 +11,13 @@ import (
|
|||
type Error interface {
|
||||
Error() string
|
||||
JSON() string
|
||||
Status() int
|
||||
Code() int
|
||||
}
|
||||
|
||||
// Err implements the Error interface
|
||||
type Err struct {
|
||||
err string
|
||||
status int
|
||||
err string
|
||||
code int
|
||||
}
|
||||
|
||||
// Error returns the error string
|
||||
|
|
@ -27,29 +27,31 @@ func (a Err) Error() string {
|
|||
|
||||
// Error returns the error string as JSON format
|
||||
func (a Err) JSON() string {
|
||||
return `{"error":"` + a.err + `","status":` + strconv.Itoa(a.status) + `}`
|
||||
return `{"error":"` + a.err + `","code":` + strconv.Itoa(a.code) + `}`
|
||||
}
|
||||
|
||||
// Status returns the HTTP status code
|
||||
func (a Err) Status() int {
|
||||
return a.status
|
||||
// Code returns the HTTP status code
|
||||
func (a Err) Code() int {
|
||||
return a.code
|
||||
}
|
||||
|
||||
func newError(err string, status int) Error {
|
||||
func newError(err string, code int) Error {
|
||||
return Err{
|
||||
err: err,
|
||||
status: status,
|
||||
err: err,
|
||||
code: code,
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
ErrInvalidAPIRoute = newError("invalid api route", http.StatusNotFound)
|
||||
ErrBadreq = newError("bad request", http.StatusBadRequest)
|
||||
ErrForbidden = newError("forbidden", http.StatusForbidden)
|
||||
ErrNotFound = newError("not found", http.StatusNotFound)
|
||||
ErrKeyNotFound = newError("key not found", http.StatusNotFound)
|
||||
ErrReqNotFound = newError("key request not found", http.StatusNotFound)
|
||||
ErrTooManyApprovals = newError("too many approvals pending", http.StatusConflict)
|
||||
ErrMNA = newError("method not allowed", http.StatusMethodNotAllowed)
|
||||
ErrISE = newError("internal server error", http.StatusInternalServerError)
|
||||
ErrInvalidAPIRoute = newError("invalid api route", http.StatusNotFound)
|
||||
ErrInvalid = newError("invalid data", http.StatusBadRequest)
|
||||
ErrBadreq = newError("bad request", http.StatusBadRequest)
|
||||
ErrForbidden = newError("forbidden", http.StatusForbidden)
|
||||
ErrNotFound = newError("not found", http.StatusNotFound)
|
||||
ErrKeyNotFound = newError("key not found", http.StatusNotFound)
|
||||
ErrReqNotFound = newError("key request not found", http.StatusNotFound)
|
||||
ErrTooManyApprovals = newError("too many approvals pending", http.StatusConflict)
|
||||
ErrKeyRequestRejected = newError("key request rejected", http.StatusConflict)
|
||||
ErrMNA = newError("method not allowed", http.StatusMethodNotAllowed)
|
||||
ErrISE = newError("internal server error", http.StatusInternalServerError)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import (
|
|||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
apiclient "git.giftfish.de/ston1th/keyctl/pkg/api/client"
|
||||
"git.giftfish.de/ston1th/keyctl/pkg/api/v1/schema"
|
||||
|
|
@ -15,7 +16,8 @@ import (
|
|||
|
||||
var (
|
||||
ErrClientIsNil = errors.New("client is nil")
|
||||
base = schema.LBPath
|
||||
keyPath = schema.KeyPath
|
||||
reqPath = schema.ReqPath
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
|
|
@ -29,42 +31,66 @@ func NewClient(c *apiclient.Client) (*Client, error) {
|
|||
return &Client{c}, nil
|
||||
}
|
||||
|
||||
func (c *Client) GetLoadBalancers(ctx context.Context) (lbs map[string]*schema.LoadBalancer, err error) {
|
||||
return c.GetLoadBalancersWithCluster(ctx, "")
|
||||
}
|
||||
|
||||
func (c *Client) GetLoadBalancersWithCluster(ctx context.Context, cluster string) (lbs map[string]*schema.LoadBalancer, err error) {
|
||||
path := base
|
||||
if cluster != "" {
|
||||
path += "/" + cluster
|
||||
}
|
||||
func (c *Client) GetApprovals(ctx context.Context) (approvals schema.Approvals, err error) {
|
||||
path := reqPath
|
||||
req, err := c.c.NewRequest(ctx, "GET", path, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
lbs = make(map[string]*schema.LoadBalancer)
|
||||
_, err = c.c.Do(req, &lbs)
|
||||
_, err = c.c.Do(req, &approvals)
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Client) GetLoadBalancer(ctx context.Context, cluster, name string) (lb *schema.LoadBalancer, err error) {
|
||||
path := fmt.Sprintf("%s/%s/%s", base, cluster, name)
|
||||
func (c *Client) RejectApproval(ctx context.Context, id string) error {
|
||||
return c.sendApproval(ctx, id, false)
|
||||
}
|
||||
|
||||
func (c *Client) AcceptApproval(ctx context.Context, id string) error {
|
||||
return c.sendApproval(ctx, id, true)
|
||||
}
|
||||
|
||||
func (c *Client) sendApproval(ctx context.Context, id string, approve bool) (err error) {
|
||||
path := fmt.Sprintf("%s/%s", reqPath, id)
|
||||
method := "DELETE"
|
||||
if approve {
|
||||
method = "PUT"
|
||||
}
|
||||
req, err := c.c.NewRequest(ctx, method, path, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
_, err = c.c.Do(req, nil)
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Client) GetKeys(ctx context.Context) (keys schema.Keys, err error) {
|
||||
path := keyPath
|
||||
req, err := c.c.NewRequest(ctx, "GET", path, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
_, err = c.c.Do(req, &lb)
|
||||
_, err = c.c.Do(req, &keys)
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Client) CreateLoadBalancer(ctx context.Context, cluster, name string, lb *schema.LoadBalancer) (err error) {
|
||||
func (c *Client) GetKey(ctx context.Context, id string) (key schema.Key, err error) {
|
||||
path := fmt.Sprintf("%s/%s", keyPath, id)
|
||||
req, err := c.c.NewRequest(ctx, "GET", path, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
_, err = c.c.FollowRedirect(req, &key, time.Second*2)
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Client) CreateKey(ctx context.Context, key schema.NewKey) (err error) {
|
||||
buf := new(bytes.Buffer)
|
||||
path := fmt.Sprintf("%s/%s/%s", base, cluster, name)
|
||||
err = lb.ValidateClient()
|
||||
path := keyPath
|
||||
err = key.Validate()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = json.NewEncoder(buf).Encode(lb)
|
||||
err = json.NewEncoder(buf).Encode(key)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
|
@ -76,21 +102,8 @@ func (c *Client) CreateLoadBalancer(ctx context.Context, cluster, name string, l
|
|||
return
|
||||
}
|
||||
|
||||
func (c *Client) DeleteLoadBalancer(ctx context.Context, cluster, name string) (err error) {
|
||||
path := fmt.Sprintf("%s/%s/%s", base, cluster, name)
|
||||
req, err := c.c.NewRequest(ctx, "DELETE", path, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
_, err = c.c.Do(req, nil)
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Client) DeleteLoadBalancersWithCluster(ctx context.Context, cluster string) (err error) {
|
||||
if cluster == "" {
|
||||
return errors.New("cluster value can not be empty")
|
||||
}
|
||||
path := base + "/" + cluster
|
||||
func (c *Client) DeleteKey(ctx context.Context, id string) (err error) {
|
||||
path := fmt.Sprintf("%s/%s", keyPath, id)
|
||||
req, err := c.c.NewRequest(ctx, "DELETE", path, nil)
|
||||
if err != nil {
|
||||
return
|
||||
|
|
|
|||
|
|
@ -54,17 +54,20 @@ type Key struct {
|
|||
Created int64 `json:"created"`
|
||||
Size int `json:"size"`
|
||||
Encoding core.Encoding `json:"encoding"`
|
||||
Key string `json:"key"`
|
||||
Key string `json:"key,omitempty"`
|
||||
}
|
||||
|
||||
type Keys []Key
|
||||
|
||||
func (Keys) Less(a, b Key) { return a.Name < b.Name }
|
||||
func (k Keys) Sort() { slices.SortStableFunc(k, k.Less) }
|
||||
func (Keys) Less(a, b Key) bool { return a.Name < b.Name }
|
||||
func (k Keys) Sort() { slices.SortStableFunc(k, k.Less) }
|
||||
|
||||
func NewKeys(keys core.Keys) (k Keys) {
|
||||
k = Keys(keys)
|
||||
for _, key := range keys {
|
||||
k = append(k, Key(key))
|
||||
}
|
||||
k.Sort()
|
||||
return
|
||||
}
|
||||
|
||||
type Approval struct {
|
||||
|
|
@ -77,8 +80,8 @@ type Approval struct {
|
|||
|
||||
type Approvals []Approval
|
||||
|
||||
func (Approvals) Less(a, b Approval) { return a.Name < b.Name }
|
||||
func (a Approvals) Sort() { slices.SortStableFunc(a, a.Less) }
|
||||
func (Approvals) Less(a, b Approval) bool { return a.Name < b.Name }
|
||||
func (a Approvals) Sort() { slices.SortStableFunc(a, a.Less) }
|
||||
|
||||
func NewApprovals(m types.ApprovalMap) (a Approvals) {
|
||||
for k, v := range m {
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import (
|
|||
|
||||
"git.giftfish.de/ston1th/keyctl/pkg/api/types"
|
||||
"git.giftfish.de/ston1th/keyctl/pkg/api/v1/schema"
|
||||
"git.giftfish.de/ston1th/keyctl/pkg/store"
|
||||
)
|
||||
|
||||
func local(h types.CtxHandler) types.CtxHandler {
|
||||
|
|
@ -53,7 +54,7 @@ func newHandler(ctx *types.Context) {
|
|||
}
|
||||
key, err := ctx.Data.DB.CreateKey(newKey.Name, newKey.Size, newKey.Encoding)
|
||||
if err != nil {
|
||||
ctx.Log.Error(err, "error writing loadbalancer", "cluster", cl, "name", n)
|
||||
ctx.Log.Error(err, "error writing key", "name", newKey.Name)
|
||||
ctx.Err(types.ErrISE)
|
||||
return
|
||||
}
|
||||
|
|
@ -65,8 +66,11 @@ func keyHandler(ctx *types.Context) {
|
|||
id := ctx.Var("id")
|
||||
k, err := ctx.Data.DB.GetKey(id)
|
||||
if err != nil {
|
||||
//TODO error 404
|
||||
ctx.Log.Error(err, "error reading key", "id", id)
|
||||
if err == store.ErrKeyNotFound {
|
||||
ctx.Err(types.ErrNotFound)
|
||||
return
|
||||
}
|
||||
ctx.Err(types.ErrISE)
|
||||
return
|
||||
}
|
||||
|
|
@ -74,7 +78,7 @@ func keyHandler(ctx *types.Context) {
|
|||
case "GET":
|
||||
aid := ctx.Var("aid")
|
||||
if aid == "" {
|
||||
aid, err = ctx.Data.Approvals.New(id, k.Name)
|
||||
aid, err = ctx.Data.Approver.New(id, k.Name)
|
||||
if err != nil {
|
||||
ctx.Log.Error(err, "error creating approval", "id", id, "name", k.Name)
|
||||
ctx.Err(types.ErrISE)
|
||||
|
|
@ -83,14 +87,22 @@ func keyHandler(ctx *types.Context) {
|
|||
ctx.Redirect(schema.ApproveURL(id, aid), http.StatusFound)
|
||||
return
|
||||
}
|
||||
if !ctx.Data.Approvals.Approved(id, aid) {
|
||||
s := ctx.Data.Approver.Status(id, aid)
|
||||
switch s {
|
||||
case types.Pending:
|
||||
ctx.Redirect(schema.ApproveURL(id, aid), http.StatusFound)
|
||||
return
|
||||
case types.Rejected:
|
||||
ctx.Err(types.ErrKeyRequestRejected)
|
||||
return
|
||||
}
|
||||
k, err := ctx.Data.DB.GetKeyWithSecret(id)
|
||||
if err != nil {
|
||||
//TODO error 404
|
||||
ctx.Log.Error(err, "error reading key", "id", id)
|
||||
if err == store.ErrKeyNotFound {
|
||||
ctx.Err(types.ErrNotFound)
|
||||
return
|
||||
}
|
||||
ctx.Err(types.ErrISE)
|
||||
return
|
||||
}
|
||||
|
|
@ -99,6 +111,10 @@ func keyHandler(ctx *types.Context) {
|
|||
err = ctx.Data.DB.DeleteKey(id)
|
||||
if err != nil {
|
||||
ctx.Log.Error(err, "error deleting key", "id", id, "name", k.Name)
|
||||
if err == store.ErrKeyNotFound {
|
||||
ctx.Err(types.ErrNotFound)
|
||||
return
|
||||
}
|
||||
ctx.Err(types.ErrISE)
|
||||
return
|
||||
}
|
||||
|
|
@ -107,14 +123,13 @@ func keyHandler(ctx *types.Context) {
|
|||
}
|
||||
|
||||
func reqListHandler(ctx *types.Context) {
|
||||
l := ctx.Data.Approvals.List()
|
||||
l := ctx.Data.Approver.List()
|
||||
ctx.JSON(schema.NewApprovals(l))
|
||||
}
|
||||
|
||||
func keyListHandler(ctx *types.Context) {
|
||||
k, err := ctx.Data.DB.GetKeys()
|
||||
if err != nil {
|
||||
//TODO error 404
|
||||
ctx.Log.Error(err, "error reading keys")
|
||||
ctx.Err(types.ErrISE)
|
||||
return
|
||||
|
|
@ -131,6 +146,6 @@ func reqHandler(ctx *types.Context) {
|
|||
case "DELETE":
|
||||
status = types.Rejected
|
||||
}
|
||||
ctx.Data.Approvals.Update(id, status)
|
||||
ctx.Data.Approver.Update(id, status)
|
||||
ctx.OK()
|
||||
}
|
||||
|
|
|
|||
136
pkg/cli/cli.go
136
pkg/cli/cli.go
|
|
@ -1,3 +1,139 @@
|
|||
// Copyright (C) 2023 Marius Schellenberger
|
||||
|
||||
package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
apiclient "git.giftfish.de/ston1th/keyctl/pkg/api/client"
|
||||
"git.giftfish.de/ston1th/keyctl/pkg/api/v1/client"
|
||||
"git.giftfish.de/ston1th/keyctl/pkg/api/v1/schema"
|
||||
"git.giftfish.de/ston1th/keyctl/pkg/core"
|
||||
)
|
||||
|
||||
func exit(err error) {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func Run(arg string) {
|
||||
opts, err := apiclient.ClientOptionsFromEnv()
|
||||
if err != nil {
|
||||
exit(err)
|
||||
}
|
||||
apic := apiclient.NewClient(opts...)
|
||||
c, err := client.NewClient(apic)
|
||||
if err != nil {
|
||||
exit(err)
|
||||
}
|
||||
ctx := context.Background()
|
||||
switch arg {
|
||||
case "new":
|
||||
name, encoding, size := newKeyFlags()
|
||||
newKey := schema.NewKey{
|
||||
Name: name,
|
||||
Size: size,
|
||||
Encoding: core.EncodingFromString(encoding),
|
||||
}
|
||||
err = c.CreateKey(ctx, newKey)
|
||||
case "del":
|
||||
id := idFlags("key id")
|
||||
err = c.DeleteKey(ctx, id)
|
||||
case "approve":
|
||||
id := idFlags("approval id")
|
||||
err = c.AcceptApproval(ctx, id)
|
||||
case "reject":
|
||||
id := idFlags("approval id")
|
||||
err = c.RejectApproval(ctx, id)
|
||||
case "get":
|
||||
id, json := getFlags()
|
||||
key, err := c.GetKey(ctx, id)
|
||||
if err != nil {
|
||||
exit(err)
|
||||
}
|
||||
if json {
|
||||
jsonOutput(key) // does not return
|
||||
}
|
||||
fmt.Println(key.Key)
|
||||
case "ls":
|
||||
json := outputFlags()
|
||||
keys, err := c.GetKeys(ctx)
|
||||
if err != nil {
|
||||
exit(err)
|
||||
}
|
||||
if json {
|
||||
jsonOutput(keys) // does not return
|
||||
}
|
||||
for _, k := range keys {
|
||||
fmt.Printf("%s %s %d %d %s\n",
|
||||
k.ID,
|
||||
k.Name,
|
||||
k.Size,
|
||||
k.Encoding,
|
||||
k.Created,
|
||||
)
|
||||
}
|
||||
case "req":
|
||||
json := outputFlags()
|
||||
approvals, err := c.GetApprovals(ctx)
|
||||
if err != nil {
|
||||
exit(err)
|
||||
}
|
||||
if json {
|
||||
jsonOutput(approvals) // does not return
|
||||
}
|
||||
for _, a := range approvals {
|
||||
fmt.Printf("%s %s %s %d %s\n",
|
||||
a.ID,
|
||||
a.AID,
|
||||
a.Name,
|
||||
a.Status,
|
||||
a.Created,
|
||||
)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
exit(err)
|
||||
}
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
func jsonOutput(v any) {
|
||||
b, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
exit(err)
|
||||
}
|
||||
fmt.Println(string(b))
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
func idFlags(desc string) (id string) {
|
||||
flag.StringVar(&id, "id", "", desc)
|
||||
flag.Parse()
|
||||
return
|
||||
}
|
||||
|
||||
func getFlags() (id string, json bool) {
|
||||
flag.StringVar(&id, "id", "", "key id")
|
||||
flag.BoolVar(&json, "json", false, "json output")
|
||||
flag.Parse()
|
||||
return
|
||||
}
|
||||
|
||||
func outputFlags() (json bool) {
|
||||
flag.BoolVar(&json, "json", false, "json output")
|
||||
flag.Parse()
|
||||
return
|
||||
}
|
||||
|
||||
func newKeyFlags() (name, encoding string, size int) {
|
||||
flag.StringVar(&name, "name", "", "key name")
|
||||
flag.StringVar(&encoding, "enc", "hex", "key encoding scheme [hex|base32|base64|base64url]")
|
||||
flag.IntVar(&size, "size", 16, "key size in bytes")
|
||||
flag.Parse()
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import (
|
|||
"path/filepath"
|
||||
|
||||
"git.giftfish.de/ston1th/godrop/v2"
|
||||
"git.giftfish.de/ston1th/keyctl/pkg/core"
|
||||
"git.giftfish.de/ston1th/keyctl/pkg/store"
|
||||
"github.com/go-logr/logr"
|
||||
)
|
||||
|
|
@ -19,9 +18,9 @@ type DB struct {
|
|||
store store.Store
|
||||
}
|
||||
|
||||
func NewPlain(log logr.Logger, cfg *core.Config) (db *DB, err error) {
|
||||
func NewPlain(log logr.Logger, dir string) (db *DB, err error) {
|
||||
db = &DB{log: log}
|
||||
dbFile := filepath.Join(cfg.DataDir, storeFile)
|
||||
dbFile := filepath.Join(dir, storeFile)
|
||||
err = godrop.Unveil(dbFile, "rwc")
|
||||
if err != nil {
|
||||
return
|
||||
|
|
@ -30,8 +29,8 @@ func NewPlain(log logr.Logger, cfg *core.Config) (db *DB, err error) {
|
|||
return
|
||||
}
|
||||
|
||||
func New(log logr.Logger, cfg *core.Config) (db *DB, err error) {
|
||||
db, err = NewPlain(log, cfg)
|
||||
func New(log logr.Logger, dir string) (db *DB, err error) {
|
||||
db, err = NewPlain(log, dir)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,9 +3,7 @@
|
|||
package db
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"git.giftfish.de/ston1th/goacc/pkg/core"
|
||||
"git.giftfish.de/ston1th/keyctl/pkg/core"
|
||||
"git.giftfish.de/ston1th/keyctl/pkg/key"
|
||||
)
|
||||
|
||||
|
|
@ -13,13 +11,12 @@ const keyPrefix = "key/"
|
|||
|
||||
func (db *DB) GetKeys() (keys core.Keys, err error) {
|
||||
err = db.store.ForEachPrefix(keyPrefix, func(k string, _ []byte) error {
|
||||
k, err := db.GetKey(k)
|
||||
key, err := db.GetKey(k)
|
||||
if err == nil {
|
||||
keys = append(keys, k)
|
||||
keys = append(keys, key)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
sort.Sort(keys)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright (C) 2022 Marius Schellenberger
|
||||
// Copyright (C) 2023 Marius Schellenberger
|
||||
|
||||
package db
|
||||
|
||||
|
|
|
|||
|
|
@ -40,8 +40,8 @@ func Generate(name string, size int, enc core.Encoding) (k core.Key, err error)
|
|||
Name: name,
|
||||
ID: id,
|
||||
Size: size,
|
||||
Encoding: enc.String(),
|
||||
Key: enc.Encode(key),
|
||||
Encoding: enc,
|
||||
Key: enc.EncodeToString(key),
|
||||
Created: time.Now().Unix(),
|
||||
}
|
||||
return
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright (C) 2022 Marius Schellenberger
|
||||
// Copyright (C) 2023 Marius Schellenberger
|
||||
|
||||
package store
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright (C) 2022 Marius Schellenberger
|
||||
// Copyright (C) 2023 Marius Schellenberger
|
||||
|
||||
package store
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright (C) 2022 Marius Schellenberger
|
||||
// Copyright (C) 2023 Marius Schellenberger
|
||||
|
||||
package store
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright (C) 2022 Marius Schellenberger
|
||||
// Copyright (C) 2023 Marius Schellenberger
|
||||
|
||||
package store
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright (C) 2022 Marius Schellenberger
|
||||
// Copyright (C) 2023 Marius Schellenberger
|
||||
|
||||
package store
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright (C) 2022 Marius Schellenberger
|
||||
// Copyright (C) 2023 Marius Schellenberger
|
||||
|
||||
package store
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright (C) 2022 Marius Schellenberger
|
||||
// Copyright (C) 2023 Marius Schellenberger
|
||||
|
||||
package store
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue