aboutsummaryrefslogtreecommitdiff
path: root/garage.go
diff options
context:
space:
mode:
authorQuentin <quentin@dufour.io>2023-04-19 13:11:46 +0000
committerQuentin <quentin@dufour.io>2023-04-19 13:11:46 +0000
commit1e75c21b65021da0c3c5a8be9be12114a2327464 (patch)
tree978a49da113a050c60bd8a803e0d641255f82ede /garage.go
parent02670ba6a60af8db1818d6d3adaafc7810d14689 (diff)
parent83ed187dbc6d1ef5f08d74aa57a7d17e907581c9 (diff)
downloadguichet-1e75c21b65021da0c3c5a8be9be12114a2327464.tar.gz
guichet-1e75c21b65021da0c3c5a8be9be12114a2327464.zip
Merge pull request 'Manage Garage Websites from Guichet' (#19) from website into main
Reviewed-on: https://git.deuxfleurs.fr/Deuxfleurs/guichet/pulls/19
Diffstat (limited to 'garage.go')
-rw-r--r--garage.go251
1 files changed, 251 insertions, 0 deletions
diff --git a/garage.go b/garage.go
new file mode 100644
index 0000000..1ae02e4
--- /dev/null
+++ b/garage.go
@@ -0,0 +1,251 @@
+package main
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ garage "git.deuxfleurs.fr/garage-sdk/garage-admin-sdk-golang"
+ "github.com/go-ldap/ldap/v3"
+ "github.com/gorilla/mux"
+ "log"
+ "net/http"
+ "strings"
+)
+
+func gadmin() (*garage.APIClient, context.Context) {
+ // Set Host and other parameters
+ configuration := garage.NewConfiguration()
+ configuration.Host = config.S3AdminEndpoint
+
+ // We can now generate a client
+ client := garage.NewAPIClient(configuration)
+
+ // Authentication is handled through the context pattern
+ ctx := context.WithValue(context.Background(), garage.ContextAccessToken, config.S3AdminToken)
+ return client, ctx
+}
+
+func grgCreateKey(name string) (*garage.KeyInfo, error) {
+ client, ctx := gadmin()
+
+ kr := garage.AddKeyRequest{Name: &name}
+ resp, _, err := client.KeyApi.AddKey(ctx).AddKeyRequest(kr).Execute()
+ if err != nil {
+ fmt.Printf("%+v\n", err)
+ return nil, err
+ }
+ return resp, nil
+}
+
+func grgGetKey(accessKey string) (*garage.KeyInfo, error) {
+ client, ctx := gadmin()
+
+ resp, _, err := client.KeyApi.GetKey(ctx, accessKey).Execute()
+ if err != nil {
+ fmt.Printf("%+v\n", err)
+ return nil, err
+ }
+ return resp, nil
+}
+
+func grgCreateWebsite(gkey, bucket string) (*garage.BucketInfo, error) {
+ client, ctx := gadmin()
+
+ br := garage.NewCreateBucketRequest()
+ br.SetGlobalAlias(bucket)
+
+ // Create Bucket
+ binfo, _, err := client.BucketApi.CreateBucket(ctx).CreateBucketRequest(*br).Execute()
+ if err != nil {
+ fmt.Printf("%+v\n", err)
+ return nil, err
+ }
+
+ // Allow user's key
+ ar := garage.AllowBucketKeyRequest{
+ BucketId: *binfo.Id,
+ AccessKeyId: gkey,
+ Permissions: *garage.NewAllowBucketKeyRequestPermissions(true, true, true),
+ }
+ binfo, _, err = client.BucketApi.AllowBucketKey(ctx).AllowBucketKeyRequest(ar).Execute()
+ if err != nil {
+ fmt.Printf("%+v\n", err)
+ return nil, err
+ }
+
+ // Expose website and set quota
+ wr := garage.NewUpdateBucketRequestWebsiteAccess()
+ wr.SetEnabled(true)
+ wr.SetIndexDocument("index.html")
+ wr.SetErrorDocument("error.html")
+
+ qr := garage.NewUpdateBucketRequestQuotas()
+ qr.SetMaxSize(1024 * 1024 * 50) // 50MB
+ qr.SetMaxObjects(10000) //10k objects
+
+ ur := garage.NewUpdateBucketRequest()
+ ur.SetWebsiteAccess(*wr)
+ ur.SetQuotas(*qr)
+
+ binfo, _, err = client.BucketApi.UpdateBucket(ctx, *binfo.Id).UpdateBucketRequest(*ur).Execute()
+ if err != nil {
+ fmt.Printf("%+v\n", err)
+ return nil, err
+ }
+
+ // Return updated binfo
+ return binfo, nil
+}
+
+func grgGetBucket(bid string) (*garage.BucketInfo, error) {
+ client, ctx := gadmin()
+
+ resp, _, err := client.BucketApi.GetBucketInfo(ctx, bid).Execute()
+ if err != nil {
+ fmt.Printf("%+v\n", err)
+ return nil, err
+ }
+ return resp, nil
+
+}
+
+func checkLoginAndS3(w http.ResponseWriter, r *http.Request) (*LoginStatus, *garage.KeyInfo, error) {
+ login := checkLogin(w, r)
+ if login == nil {
+ return nil, nil, errors.New("LDAP login failed")
+ }
+
+ keyID := login.UserEntry.GetAttributeValue("garage_s3_access_key")
+ if keyID == "" {
+ keyPair, err := grgCreateKey(login.Info.Username)
+ if err != nil {
+ return login, nil, err
+ }
+ modify_request := ldap.NewModifyRequest(login.Info.DN, nil)
+ modify_request.Replace("garage_s3_access_key", []string{*keyPair.AccessKeyId})
+ // @FIXME compatibility feature for bagage (SFTP+webdav)
+ // you can remove it once bagage will be updated to fetch the key from garage directly
+ // or when bottin will be able to dynamically fetch it.
+ modify_request.Replace("garage_s3_secret_key", []string{*keyPair.SecretAccessKey})
+ err = login.conn.Modify(modify_request)
+ return login, keyPair, err
+ }
+ // Note: we could simply return the login info, but LX asked we do not
+ // store the secrets in LDAP in the future.
+ keyPair, err := grgGetKey(keyID)
+ return login, keyPair, err
+}
+
+type keyView struct {
+ Status *LoginStatus
+ Key *garage.KeyInfo
+}
+
+func handleGarageKey(w http.ResponseWriter, r *http.Request) {
+ login, s3key, err := checkLoginAndS3(w, r)
+ if err != nil {
+ log.Println(err)
+ return
+ }
+ view := keyView{Status: login, Key: s3key}
+
+ tKey := getTemplate("garage_key.html")
+ tKey.Execute(w, &view)
+}
+
+type webListView struct {
+ Status *LoginStatus
+ Key *garage.KeyInfo
+}
+
+func handleGarageWebsiteList(w http.ResponseWriter, r *http.Request) {
+ login, s3key, err := checkLoginAndS3(w, r)
+ if err != nil {
+ log.Println(err)
+ return
+ }
+ view := webListView{Status: login, Key: s3key}
+
+ tWebsiteList := getTemplate("garage_website_list.html")
+ tWebsiteList.Execute(w, &view)
+}
+
+func handleGarageWebsiteNew(w http.ResponseWriter, r *http.Request) {
+ _, s3key, err := checkLoginAndS3(w, r)
+ if err != nil {
+ log.Println(err)
+ return
+ }
+
+ tWebsiteNew := getTemplate("garage_website_new.html")
+ if r.Method == "POST" {
+ r.ParseForm()
+ log.Println(r.Form)
+
+ bucket := strings.Join(r.Form["bucket"], "")
+ if bucket == "" {
+ bucket = strings.Join(r.Form["bucket2"], "")
+ }
+ if bucket == "" {
+ log.Println("Form empty")
+ // @FIXME we need to return the error to the user
+ tWebsiteNew.Execute(w, nil)
+ return
+ }
+
+ binfo, err := grgCreateWebsite(*s3key.AccessKeyId, bucket)
+ if err != nil {
+ log.Println(err)
+ // @FIXME we need to return the error to the user
+ tWebsiteNew.Execute(w, nil)
+ return
+ }
+
+ http.Redirect(w, r, "/garage/website/b/"+*binfo.Id, http.StatusFound)
+ return
+ }
+
+ tWebsiteNew.Execute(w, nil)
+}
+
+type webInspectView struct {
+ Status *LoginStatus
+ Key *garage.KeyInfo
+ Bucket *garage.BucketInfo
+ IndexDoc string
+ ErrorDoc string
+ MaxObjects int64
+ MaxSize int64
+ UsedSizePct float64
+}
+
+func handleGarageWebsiteInspect(w http.ResponseWriter, r *http.Request) {
+ login, s3key, err := checkLoginAndS3(w, r)
+ if err != nil {
+ log.Println(err)
+ return
+ }
+
+ bucketId := mux.Vars(r)["bucket"]
+ binfo, err := grgGetBucket(bucketId)
+ if err != nil {
+ log.Println(err)
+ return
+ }
+
+ wc := binfo.GetWebsiteConfig()
+ q := binfo.GetQuotas()
+
+ view := webInspectView{
+ Status: login,
+ Key: s3key,
+ Bucket: binfo,
+ IndexDoc: (&wc).GetIndexDocument(),
+ ErrorDoc: (&wc).GetErrorDocument(),
+ MaxObjects: (&q).GetMaxObjects(),
+ MaxSize: (&q).GetMaxSize(),
+ }
+
+ tWebsiteInspect := getTemplate("garage_website_inspect.html")
+ tWebsiteInspect.Execute(w, &view)
+}