From 37a9f6fa54fc8e237ea117d8d0e1480795c5c831 Mon Sep 17 00:00:00 2001 From: Quentin Dufour Date: Mon, 24 Jun 2024 07:53:06 +0200 Subject: [PATCH 1/9] inject cacert --- flake.nix | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/flake.nix b/flake.nix index 5d69f9f..3b6ad3b 100644 --- a/flake.nix +++ b/flake.nix @@ -12,9 +12,6 @@ system = "x86_64-linux"; overlays = [ (import "${gomod2nix}/overlay.nix") - /*(self: super: { - gomod = super.callPackage "${gomod2nix}/builder/" { }; - })*/ ]; }; src = ./.; @@ -38,10 +35,16 @@ platforms = platforms.linux; }; }; + + container = pkgs.dockerTools.buildImage { name = "dxflrs/guichet"; + copyToRoot = pkgs.buildEnv { + name = "guichet-env"; + paths = [ guichet pkgs.cacert ]; + }; config = { - Entrypoint = "${guichet}/bin/guichet"; + Entrypoint = "/bin/guichet"; }; }; in { From 793cb2d3c20d47f3a74f385c4b9aeadf2e2297b0 Mon Sep 17 00:00:00 2001 From: Quentin Dufour Date: Mon, 24 Jun 2024 08:17:15 +0200 Subject: [PATCH 2/9] update dev env skeleton --- config.json.example | 18 ++++++++---------- integration/docker-compose.yml | 10 ++++++++-- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/config.json.example b/config.json.example index 1760685..2d40aac 100644 --- a/config.json.example +++ b/config.json.example @@ -10,25 +10,23 @@ "invitation_base_dn": "ou=invitations,dc=bottin,dc=eu", "invitation_name_attr": "cn", - "invited_mail_format": "{}@example.com", - "invited_auto_groups": [ - "cn=email,ou=groups,dc=bottin,dc=eu" - ], + "invited_mail_format": "{}@bottin.eu", + "invited_auto_groups": [ ], - "web_address": "http://guichet.localhost:9991", - "mail_from": "welcome@example.com", - "smtp_server": "smtp.example.com", + "web_address": "http://localhost:9991", + "mail_from": "welcome@bottin.eu", + "smtp_server": "smtp.bottin.eu", "smtp_username": "guichet", "smtp_password": "", "admin_account": "cn=admin,dc=bottin,dc=eu", - "group_can_admin": "gid=admin,ou=groups,dc=bottin,dc=eu", - "group_can_invite": "", + "group_can_admin": "cn=admin,ou=groups,dc=bottin,dc=eu", + "group_can_invite": "cn=admin,ou=groups,dc=bottin,dc=eu", "s3_admin_endpoint": "localhost:3903", "s3_admin_token": "GlXP43PWH3LuvEGSNxKYzZCyUss8VqZmarBU+HUlrxw=", - "s3_endpoint": "localhost", + "s3_endpoint": "localhost:3900", "s3_access_key": "", "s3_secret_key": "", "s3_region": "garage", diff --git a/integration/docker-compose.yml b/integration/docker-compose.yml index ec855db..e44a723 100644 --- a/integration/docker-compose.yml +++ b/integration/docker-compose.yml @@ -1,11 +1,15 @@ version: '3' services: consul: - image: hashicorp/consul:1.16 + # sync with nixos stable packages assuming our stack is up to date + # https://search.nixos.org/packages?channel=24.05&from=0&size=50&sort=relevance&type=packages&query=consul + image: hashicorp/consul:1.18 restart: "always" expose: - 8500 bottin: + # sync with deuxfleurs/nixcfg/cluster/prod/app/core/deploy/bottin.hcl + # to ensure compatibility with prod image: dxflrs/bottin:7h18i30cckckaahv87d3c86pn4a7q41z #command: "-config /etc/bottin.json" restart: "always" @@ -15,7 +19,9 @@ services: volumes: - "./config/bottin.json:/config.json" garage: - image: dxflrs/garage:v0.8.2 + # sync with deuxfleurs/nixcfg/cluster/prod/app/garage/deploy/garage.hcl + # to ensure compatibility with prod + image: superboum/garage:v1.0.0-rc1-hotfix-red-ftr-wquorum ports: - "3900:3900" - "3902:3902" From 9cd06c95eba491bce1a60f7d1b2fef320aec1124 Mon Sep 17 00:00:00 2001 From: Quentin Dufour Date: Mon, 24 Jun 2024 08:44:22 +0200 Subject: [PATCH 3/9] don't display the global key anymore --- garage.go | 181 -------------------- main.go | 1 - templates/garage_key.html | 234 -------------------------- templates/garage_website_inspect.html | 34 ++++ templates/home.html | 3 +- website.go | 27 ++- webui_website.go | 175 +++++++++++++++++++ 7 files changed, 223 insertions(+), 432 deletions(-) delete mode 100644 templates/garage_key.html create mode 100644 webui_website.go diff --git a/garage.go b/garage.go index 7cd879b..f9529e9 100644 --- a/garage.go +++ b/garage.go @@ -4,10 +4,7 @@ import ( "context" "fmt" garage "git.deuxfleurs.fr/garage-sdk/garage-admin-sdk-golang" - "github.com/gorilla/mux" "log" - "net/http" - "strings" ) func gadmin() (*garage.APIClient, context.Context) { @@ -166,181 +163,3 @@ func grgDeleteBucket(bid string) error { } return err } - -// --- Start page rendering functions - -func handleWebsiteConfigure(w http.ResponseWriter, r *http.Request) { - user := RequireUserHtml(w, r) - if user == nil { - return - } - - tKey := getTemplate("garage_key.html") - tKey.Execute(w, user) -} - -func handleWebsiteList(w http.ResponseWriter, r *http.Request) { - user := RequireUserHtml(w, r) - if user == nil { - return - } - - ctrl, err := NewWebsiteController(user) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - if len(ctrl.PrettyList) > 0 { - http.Redirect(w, r, "/website/inspect/"+ctrl.PrettyList[0], http.StatusFound) - } else { - http.Redirect(w, r, "/website/new", http.StatusFound) - } -} - -type WebsiteNewTpl struct { - Ctrl *WebsiteController - Err error -} - -func handleWebsiteNew(w http.ResponseWriter, r *http.Request) { - user := RequireUserHtml(w, r) - if user == nil { - return - } - - ctrl, err := NewWebsiteController(user) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - tpl := &WebsiteNewTpl{ctrl, nil} - - tWebsiteNew := getTemplate("garage_website_new.html") - if r.Method == "POST" { - r.ParseForm() - - bucket := strings.Join(r.Form["bucket"], "") - if bucket == "" { - bucket = strings.Join(r.Form["bucket2"], "") - } - - view, err := ctrl.Create(bucket) - if err != nil { - tpl.Err = err - tWebsiteNew.Execute(w, tpl) - return - } - - http.Redirect(w, r, "/website/inspect/"+view.Name.Pretty, http.StatusFound) - return - } - - tWebsiteNew.Execute(w, tpl) -} - -type WebsiteInspectTpl struct { - Describe *WebsiteDescribe - View *WebsiteView - Err error -} - -func handleWebsiteInspect(w http.ResponseWriter, r *http.Request) { - var processErr error - - user := RequireUserHtml(w, r) - if user == nil { - return - } - - ctrl, err := NewWebsiteController(user) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - bucketName := mux.Vars(r)["bucket"] - - if r.Method == "POST" { - r.ParseForm() - action := strings.Join(r.Form["action"], "") - switch action { - case "increase_quota": - _, processErr = ctrl.Patch(bucketName, &WebsitePatch{Size: &user.Quota.WebsiteSizeBursted}) - case "delete_bucket": - processErr = ctrl.Delete(bucketName) - if processErr == nil { - http.Redirect(w, r, "/website", http.StatusFound) - } - default: - processErr = fmt.Errorf("Unknown action") - } - - } - - view, err := ctrl.Inspect(bucketName) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - describe, err := ctrl.Describe() - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - tpl := &WebsiteInspectTpl{describe, view, processErr} - - tWebsiteInspect := getTemplate("garage_website_inspect.html") - tWebsiteInspect.Execute(w, &tpl) -} - -func handleWebsiteVhost(w http.ResponseWriter, r *http.Request) { - var processErr error - - user := RequireUserHtml(w, r) - if user == nil { - return - } - - ctrl, err := NewWebsiteController(user) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - bucketName := mux.Vars(r)["bucket"] - - if r.Method == "POST" { - r.ParseForm() - - bucket := strings.Join(r.Form["bucket"], "") - if bucket == "" { - bucket = strings.Join(r.Form["bucket2"], "") - } - - view, processErr := ctrl.Patch(bucketName, &WebsitePatch{Vhost: &bucket}) - if processErr == nil { - http.Redirect(w, r, "/website/inspect/"+view.Name.Pretty, http.StatusFound) - return - } - } - - view, err := ctrl.Inspect(bucketName) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - describe, err := ctrl.Describe() - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - tpl := &WebsiteInspectTpl{describe, view, processErr} - tWebsiteEdit := getTemplate("garage_website_edit.html") - tWebsiteEdit.Execute(w, &tpl) -} diff --git a/main.go b/main.go index 39c7f08..e1b0eb8 100644 --- a/main.go +++ b/main.go @@ -159,7 +159,6 @@ func server(args []string) { r.HandleFunc("/website", handleWebsiteList) r.HandleFunc("/website/new", handleWebsiteNew) - r.HandleFunc("/website/configure", handleWebsiteConfigure) r.HandleFunc("/website/inspect/{bucket}", handleWebsiteInspect) r.HandleFunc("/website/vhost/{bucket}", handleWebsiteVhost) diff --git a/templates/garage_key.html b/templates/garage_key.html deleted file mode 100644 index cf56822..0000000 --- a/templates/garage_key.html +++ /dev/null @@ -1,234 +0,0 @@ -{{define "title"}}Profile |{{end}} - -{{define "body"}} -
-

Mes identifiants

- Mes sites webs - Menu principal -
- - - -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - -
Identifiant de clé{{ .S3KeyInfo.AccessKeyId }}
Clé secrèteCliquer pour afficher la clé secrète
Régiongarage
Endpoint URLhttps://garage.deuxfleurs.fr
Type d'URLDNS et chemin (préférer chemin)
SignatureVersion 4
- -

Configurer votre logiciel :

- -
-
-
-

- -

-
-
-
-

Créez un fichier nommé ~/.awsrc :

-
-export AWS_ACCESS_KEY_ID={{ .S3KeyInfo.AccessKeyId }}
-export AWS_SECRET_ACCESS_KEY={{ .S3KeyInfo.SecretAccessKey }}
-export AWS_DEFAULT_REGION='garage'
-
-function aws { command aws --endpoint-url https://garage.deuxfleurs.fr $@ ; }
-aws --version
-                        
-

Ensuite vous pouvez utiliser awscli :

-
-source ~/.awsrc
-aws s3 ls
-aws s3 ls s3://my-bucket
-aws s3 cp /tmp/a.txt s3://my-bucket
-...
-                        
-
-
-
- -
-
-

- -

-
- -
-
-

Vous pouvez configurer Minio CLI avec cette commande :

-
-mc alias set \
-  garage \
-  https://garage.deuxfleurs.fr \
-  {{ .S3KeyInfo.AccessKeyId }} \
-  {{ .S3KeyInfo.SecretAccessKey }} \
-  --api S3v4
-                        
-

Et ensuite pour utiliser Minio CLI avec :

-
-mc ls garage/
-mc cp /tmp/a.txt garage/my-bucket/a.txt
-...
-                        
-
-
-
- -
-
-

- -

-
- -
-
- Reportez vous au guide -
-
-
- -
-
-

- -

-
-
-
-

Dans votre fichier config.toml, rajoutez :

-
-[[deployment.targets]]
- URL = "s3://bucket?endpoint=garage.deuxfleurs.fr&s3ForcePathStyle=true&region=garage"
-                        
-

Assurez-vous d'avoir un fichier dans lequel les variables AWS_ACCESS_KEY_ID et AWS_SECRET_ACCESS_KEY sont définies, - ici on suppose que vous avez suivi les instructions de l'outil awscli (ci-dessus) et que vous avez un fichier ~/.awsrc qui défini ces variables. - Ensuite :

-
-source ~/.awsrc
-hugo deploy
-                        
-
-
-
- -
-
-

- -

-
-
-
- Bientôt... -
-
-
-
-
- - -
- - - - - - - - - - - - - - - - - - - -
Nom d'utilisateur-ice{{ .Login.Info.Username }}
Mot de passe(votre mot de passe guichet)
Hôtesftp://bagage.deuxfleurs.fr
Port2222
-

Configurer votre logiciel :

- -
-
-
-

- -

-
-
-
-

Un exemple avec SCP :

-
-scp -oHostKeyAlgorithms=+ssh-rsa -P2222 -r ./public {{ .Login.Info.Username }}@bagage.deuxfleurs.fr:mon_bucket/
-                        
-
-
-
-
-
-

- -

-
-
-
- Bientôt -
-
-
-
- -
-
- -{{end}} diff --git a/templates/garage_website_inspect.html b/templates/garage_website_inspect.html index a8f463d..af87955 100644 --- a/templates/garage_website_inspect.html +++ b/templates/garage_website_inspect.html @@ -59,10 +59,44 @@ {{ end }}

+
Informations de connexion
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
Identifiant de clé{{ .View.AccessKeyId }}
Clé secrète + Cliquer pour afficher la clé secrète + +
Régiongarage
Endpoint URLhttps://garage.deuxfleurs.fr
Type d'URLDNS et chemin (préférer chemin)
SignatureVersion 4
+
Actions
+ Changer le nom de domaine
diff --git a/templates/home.html b/templates/home.html index dd88d13..3210a13 100644 --- a/templates/home.html +++ b/templates/home.html @@ -24,10 +24,9 @@
- Mon espace sur la toile + Mes services
diff --git a/website.go b/website.go index 74daf89..cdfae89 100644 --- a/website.go +++ b/website.go @@ -81,26 +81,17 @@ func NewWebsiteController(user *LoggedUser) (*WebsiteController, error) { } type WebsiteDescribe struct { - AccessKeyId string `json:"access_key_id"` - SecretAccessKey string `json:"secret_access_key"` AllowedWebsites *QuotaStat `json:"quota_website_count"` BurstBucketQuotaSize string `json:"burst_bucket_quota_size"` Websites []*WebsiteId `json:"vhosts"` } func (w *WebsiteController) Describe() (*WebsiteDescribe, error) { - s3key, err := w.User.S3KeyInfo() - if err != nil { - return nil, err - } - r := make([]*WebsiteId, 0, len(w.PrettyList)) for _, k := range w.PrettyList { r = append(r, w.WebsiteIdx[k]) } return &WebsiteDescribe{ - *s3key.AccessKeyId, - *s3key.SecretAccessKey, &w.WebsiteCount, w.User.Quota.WebsiteSizeBurstedPretty(), r}, nil @@ -231,9 +222,11 @@ func (w *WebsiteController) Delete(pretty string) error { } type WebsiteView struct { - Name *WebsiteId `json:"vhost"` - Size QuotaStat `json:"quota_size"` - Files QuotaStat `json:"quota_files"` + Name *WebsiteId `json:"vhost"` + AccessKeyId string `json:"access_key_id"` + SecretAccessKey string `json:"secret_access_key"` + Size QuotaStat `json:"quota_size"` + Files QuotaStat `json:"quota_files"` } func NewWebsiteView(binfo *garage.BucketInfo) *WebsiteView { @@ -242,10 +235,16 @@ func NewWebsiteView(binfo *garage.BucketInfo) *WebsiteView { wid := NewWebsiteIdFromBucketInfo(binfo) size := NewQuotaStat(*binfo.Bytes, (&q).GetMaxSize(), true) objects := NewQuotaStat(*binfo.Objects, (&q).GetMaxObjects(), false) - return &WebsiteView{wid, size, objects} + return &WebsiteView{ + wid, + "not yet implemented", + "not yet implemented", + size, + objects, + } } type WebsitePatch struct { - Size *int64 `json:"quota_size"` + Size *int64 `json:"quota_size"` Vhost *string `json:"vhost"` } diff --git a/webui_website.go b/webui_website.go new file mode 100644 index 0000000..e8a89c0 --- /dev/null +++ b/webui_website.go @@ -0,0 +1,175 @@ +package main + +import ( + "fmt" + "github.com/gorilla/mux" + "net/http" + "strings" +) + +// --- Start page rendering functions +func handleWebsiteList(w http.ResponseWriter, r *http.Request) { + user := RequireUserHtml(w, r) + if user == nil { + return + } + + ctrl, err := NewWebsiteController(user) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + if len(ctrl.PrettyList) > 0 { + http.Redirect(w, r, "/website/inspect/"+ctrl.PrettyList[0], http.StatusFound) + } else { + http.Redirect(w, r, "/website/new", http.StatusFound) + } +} + +type WebsiteNewTpl struct { + Ctrl *WebsiteController + Err error +} + +func handleWebsiteNew(w http.ResponseWriter, r *http.Request) { + user := RequireUserHtml(w, r) + if user == nil { + return + } + + ctrl, err := NewWebsiteController(user) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + tpl := &WebsiteNewTpl{ctrl, nil} + + tWebsiteNew := getTemplate("garage_website_new.html") + if r.Method == "POST" { + r.ParseForm() + + bucket := strings.Join(r.Form["bucket"], "") + if bucket == "" { + bucket = strings.Join(r.Form["bucket2"], "") + } + + view, err := ctrl.Create(bucket) + if err != nil { + tpl.Err = err + tWebsiteNew.Execute(w, tpl) + return + } + + http.Redirect(w, r, "/website/inspect/"+view.Name.Pretty, http.StatusFound) + return + } + + tWebsiteNew.Execute(w, tpl) +} + +type WebsiteInspectTpl struct { + Describe *WebsiteDescribe + View *WebsiteView + Err error +} + +func handleWebsiteInspect(w http.ResponseWriter, r *http.Request) { + var processErr error + + user := RequireUserHtml(w, r) + if user == nil { + return + } + + ctrl, err := NewWebsiteController(user) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + bucketName := mux.Vars(r)["bucket"] + + if r.Method == "POST" { + r.ParseForm() + action := strings.Join(r.Form["action"], "") + switch action { + case "increase_quota": + _, processErr = ctrl.Patch(bucketName, &WebsitePatch{Size: &user.Quota.WebsiteSizeBursted}) + case "delete_bucket": + processErr = ctrl.Delete(bucketName) + if processErr == nil { + http.Redirect(w, r, "/website", http.StatusFound) + } + default: + processErr = fmt.Errorf("Unknown action") + } + + } + + view, err := ctrl.Inspect(bucketName) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + describe, err := ctrl.Describe() + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + tpl := &WebsiteInspectTpl{describe, view, processErr} + + tWebsiteInspect := getTemplate("garage_website_inspect.html") + tWebsiteInspect.Execute(w, &tpl) +} + +func handleWebsiteVhost(w http.ResponseWriter, r *http.Request) { + var processErr error + + user := RequireUserHtml(w, r) + if user == nil { + return + } + + ctrl, err := NewWebsiteController(user) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + bucketName := mux.Vars(r)["bucket"] + + if r.Method == "POST" { + r.ParseForm() + + bucket := strings.Join(r.Form["bucket"], "") + if bucket == "" { + bucket = strings.Join(r.Form["bucket2"], "") + } + + view, processErr := ctrl.Patch(bucketName, &WebsitePatch{Vhost: &bucket}) + if processErr == nil { + http.Redirect(w, r, "/website/inspect/"+view.Name.Pretty, http.StatusFound) + return + } + } + + view, err := ctrl.Inspect(bucketName) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + describe, err := ctrl.Describe() + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + tpl := &WebsiteInspectTpl{describe, view, processErr} + tWebsiteEdit := getTemplate("garage_website_edit.html") + tWebsiteEdit.Execute(w, &tpl) +} From a7edf6d1ba812f11b7711c4bdc6dc6d9af9c7906 Mon Sep 17 00:00:00 2001 From: Quentin Dufour Date: Mon, 24 Jun 2024 09:17:09 +0200 Subject: [PATCH 4/9] bump garage SDK --- garage.go | 13 +++++++------ go.mod | 2 +- go.sum | 2 ++ login.go | 2 +- website.go | 46 +++++++++++++++++++++++++++++++++++++--------- webui_website.go | 3 +++ 6 files changed, 51 insertions(+), 17 deletions(-) diff --git a/garage.go b/garage.go index f9529e9..52a26b0 100644 --- a/garage.go +++ b/garage.go @@ -23,8 +23,9 @@ func gadmin() (*garage.APIClient, context.Context) { func grgCreateKey(name string) (*garage.KeyInfo, error) { client, ctx := gadmin() - kr := garage.AddKeyRequest{Name: &name} - resp, _, err := client.KeyApi.AddKey(ctx).AddKeyRequest(kr).Execute() + kr := garage.NewAddKeyRequest() + kr.SetName(name) + resp, _, err := client.KeyApi.AddKey(ctx).AddKeyRequest(*kr).Execute() if err != nil { fmt.Printf("%+v\n", err) return nil, err @@ -35,7 +36,7 @@ func grgCreateKey(name string) (*garage.KeyInfo, error) { func grgGetKey(accessKey string) (*garage.KeyInfo, error) { client, ctx := gadmin() - resp, _, err := client.KeyApi.GetKey(ctx, accessKey).Execute() + resp, _, err := client.KeyApi.GetKey(ctx).Id(accessKey).ShowSecretKey("true").Execute() if err != nil { fmt.Printf("%+v\n", err) return nil, err @@ -88,7 +89,7 @@ func allowWebsiteDefault() *garage.UpdateBucketRequestWebsiteAccess { func grgUpdateBucket(bid string, ur *garage.UpdateBucketRequest) (*garage.BucketInfo, error) { client, ctx := gadmin() - binfo, _, err := client.BucketApi.UpdateBucket(ctx, bid).UpdateBucketRequest(*ur).Execute() + binfo, _, err := client.BucketApi.UpdateBucket(ctx).Id(bid).UpdateBucketRequest(*ur).Execute() if err != nil { fmt.Printf("%+v\n", err) return nil, err @@ -145,7 +146,7 @@ func grgDelLocalAlias(bid, key, alias string) (*garage.BucketInfo, error) { func grgGetBucket(bid string) (*garage.BucketInfo, error) { client, ctx := gadmin() - resp, _, err := client.BucketApi.GetBucketInfo(ctx, bid).Execute() + resp, _, err := client.BucketApi.GetBucketInfo(ctx).Id(bid).Execute() if err != nil { log.Println(err) return nil, err @@ -157,7 +158,7 @@ func grgGetBucket(bid string) (*garage.BucketInfo, error) { func grgDeleteBucket(bid string) error { client, ctx := gadmin() - _, err := client.BucketApi.DeleteBucket(ctx, bid).Execute() + _, err := client.BucketApi.DeleteBucket(ctx).Id(bid).Execute() if err != nil { log.Println(err) } diff --git a/go.mod b/go.mod index 56bd9f6..86ed878 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module git.deuxfleurs.fr/Deuxfleurs/guichet go 1.18 require ( - git.deuxfleurs.fr/garage-sdk/garage-admin-sdk-golang v0.0.0-20230131081355-c965fe7f7dc9 + git.deuxfleurs.fr/garage-sdk/garage-admin-sdk-golang v0.0.0-20231128153612-8b81fae65e5e github.com/emersion/go-sasl v0.0.0-20191210011802-430746ea8b9b github.com/emersion/go-smtp v0.12.1 github.com/go-ldap/ldap/v3 v3.1.6 diff --git a/go.sum b/go.sum index ae748fd..6543905 100644 --- a/go.sum +++ b/go.sum @@ -33,6 +33,8 @@ cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= git.deuxfleurs.fr/garage-sdk/garage-admin-sdk-golang v0.0.0-20230131081355-c965fe7f7dc9 h1:ERg8KCpIKym98EOKa8Gq0NSBxsasD3sqb/R0gg1wOzU= git.deuxfleurs.fr/garage-sdk/garage-admin-sdk-golang v0.0.0-20230131081355-c965fe7f7dc9/go.mod h1:TlSL6QVxozmdRaSgP6Akspi0HCJv4HAkkq3Dldru4GM= +git.deuxfleurs.fr/garage-sdk/garage-admin-sdk-golang v0.0.0-20231128153612-8b81fae65e5e h1:h89CAh0qmUcGJykss/utXIw+yRGa3Gr6VyrZ5ZWN0kY= +git.deuxfleurs.fr/garage-sdk/garage-admin-sdk-golang v0.0.0-20231128153612-8b81fae65e5e/go.mod h1:TlSL6QVxozmdRaSgP6Akspi0HCJv4HAkkq3Dldru4GM= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= diff --git a/login.go b/login.go index 277e3ae..4bbcd65 100644 --- a/login.go +++ b/login.go @@ -221,7 +221,7 @@ func (lu *LoggedUser) S3KeyInfo() (*garage.KeyInfo, error) { // @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}) + modify_request.Replace("garage_s3_secret_key", []string{*keyPair.SecretAccessKey.Get()}) err = lu.Login.conn.Modify(modify_request) if err != nil { return nil, err diff --git a/website.go b/website.go index cdfae89..ae4ffff 100644 --- a/website.go +++ b/website.go @@ -18,7 +18,8 @@ var ( ErrBucketDeleteNotEmpty = fmt.Errorf("You must remove all the files before deleting a bucket") ErrBucketDeleteUnfinishedUpload = fmt.Errorf("You must remove all the unfinished multipart uploads before deleting a bucket") ErrCantChangeVhost = fmt.Errorf("Can't change the vhost to the desired value. Maybe it's already used by someone else or an internal error occured") - ErrCantRemoveOldVhost = fmt.Errorf("The new vhost is bound to the bucket but the old one can't be removed, this is an internal error") + ErrCantRemoveOldVhost = fmt.Errorf("The new vhost is bound to the bucket but the old one can't be removed, it's an internal error") + ErrFetchDedicatedKey = fmt.Errorf("Bucket has no dedicated key while it's required, it's an internal error") ) type WebsiteId struct { @@ -91,10 +92,12 @@ func (w *WebsiteController) Describe() (*WebsiteDescribe, error) { for _, k := range w.PrettyList { r = append(r, w.WebsiteIdx[k]) } + return &WebsiteDescribe{ &w.WebsiteCount, w.User.Quota.WebsiteSizeBurstedPretty(), - r}, nil + r, + }, nil } func (w *WebsiteController) Inspect(pretty string) (*WebsiteView, error) { @@ -108,7 +111,9 @@ func (w *WebsiteController) Inspect(pretty string) (*WebsiteView, error) { return nil, ErrFetchBucketInfo } - return NewWebsiteView(binfo), nil + // @TODO: fetch the associated key + + return NewWebsiteView(binfo, nil) } func (w *WebsiteController) Patch(pretty string, patch *WebsitePatch) (*WebsiteView, error) { @@ -152,7 +157,11 @@ func (w *WebsiteController) Patch(pretty string, patch *WebsitePatch) (*WebsiteV } } - return NewWebsiteView(binfo), nil + if patch.RotateKey != nil && *patch.RotateKey { + // @TODO: rotate key + } + + return NewWebsiteView(binfo, nil) } func (w *WebsiteController) Create(pretty string) (*WebsiteView, error) { @@ -164,11 +173,13 @@ func (w *WebsiteController) Create(pretty string) (*WebsiteView, error) { return nil, ErrWebsiteQuotaReached } + // Create bucket binfo, err := grgCreateBucket(pretty) if err != nil { return nil, ErrCantCreateBucket } + // Allow user's global key on bucket s3key, err := w.User.S3KeyInfo() if err != nil { return nil, err @@ -179,6 +190,7 @@ func (w *WebsiteController) Create(pretty string) (*WebsiteView, error) { return nil, ErrCantAllowKey } + // Set quota qr := w.User.Quota.DefaultWebsiteQuota() wr := allowWebsiteDefault() @@ -191,7 +203,10 @@ func (w *WebsiteController) Create(pretty string) (*WebsiteView, error) { return nil, ErrCantConfigureBucket } - return NewWebsiteView(binfo), nil + // Create a dedicated key + // @TODO + + return NewWebsiteView(binfo, nil) } func (w *WebsiteController) Delete(pretty string) error { @@ -204,6 +219,7 @@ func (w *WebsiteController) Delete(pretty string) error { return ErrWebsiteNotFound } + // Error checking binfo, err := grgGetBucket(website.Internal) if err != nil { return ErrFetchBucketInfo @@ -217,6 +233,10 @@ func (w *WebsiteController) Delete(pretty string) error { return ErrBucketDeleteUnfinishedUpload } + // Delete dedicated key + // @TODO + + // Actually delete bucket err = grgDeleteBucket(website.Internal) return err } @@ -229,7 +249,14 @@ type WebsiteView struct { Files QuotaStat `json:"quota_files"` } -func NewWebsiteView(binfo *garage.BucketInfo) *WebsiteView { +func NewWebsiteView(binfo *garage.BucketInfo, s3key *garage.KeyInfo) (*WebsiteView, error) { + if binfo == nil { + return nil, ErrFetchBucketInfo + } + if s3key == nil { + return nil, ErrFetchDedicatedKey + } + q := binfo.GetQuotas() wid := NewWebsiteIdFromBucketInfo(binfo) @@ -237,14 +264,15 @@ func NewWebsiteView(binfo *garage.BucketInfo) *WebsiteView { objects := NewQuotaStat(*binfo.Objects, (&q).GetMaxObjects(), false) return &WebsiteView{ wid, - "not yet implemented", - "not yet implemented", + *s3key.AccessKeyId, + *s3key.SecretAccessKey.Get(), size, objects, - } + }, nil } type WebsitePatch struct { Size *int64 `json:"quota_size"` Vhost *string `json:"vhost"` + RotateKey *bool `json:"rotate_key"` } diff --git a/webui_website.go b/webui_website.go index e8a89c0..9685374 100644 --- a/webui_website.go +++ b/webui_website.go @@ -102,6 +102,9 @@ func handleWebsiteInspect(w http.ResponseWriter, r *http.Request) { if processErr == nil { http.Redirect(w, r, "/website", http.StatusFound) } + case "rotate_key": + do_action := true + _, processErr = ctrl.Patch(bucketName, &WebsitePatch { RotateKey: &do_action }) default: processErr = fmt.Errorf("Unknown action") } From e940996f0f8cb8c6adb22c7d8780e364d65ecea2 Mon Sep 17 00:00:00 2001 From: Quentin Dufour Date: Mon, 24 Jun 2024 10:22:17 +0200 Subject: [PATCH 5/9] generate a per-website dedicated key --- garage.go | 15 +++++- login.go | 6 ++- website.go | 131 ++++++++++++++++++++++++++++++++++++++++------- webui_website.go | 2 +- 4 files changed, 131 insertions(+), 23 deletions(-) diff --git a/garage.go b/garage.go index 52a26b0..fabd6bf 100644 --- a/garage.go +++ b/garage.go @@ -44,6 +44,17 @@ func grgGetKey(accessKey string) (*garage.KeyInfo, error) { return resp, nil } +func grgSearchKey(name string) (*garage.KeyInfo, error) { + client, ctx := gadmin() + + resp, _, err := client.KeyApi.GetKey(ctx).Search(name).ShowSecretKey("true").Execute() + if err != nil { + fmt.Printf("%+v\n", err) + return nil, err + } + return resp, nil +} + func grgCreateBucket(bucket string) (*garage.BucketInfo, error) { client, ctx := gadmin() @@ -59,14 +70,14 @@ func grgCreateBucket(bucket string) (*garage.BucketInfo, error) { return binfo, nil } -func grgAllowKeyOnBucket(bid, gkey string) (*garage.BucketInfo, error) { +func grgAllowKeyOnBucket(bid, gkey string, read, write, owner bool) (*garage.BucketInfo, error) { client, ctx := gadmin() // Allow user's key ar := garage.AllowBucketKeyRequest{ BucketId: bid, AccessKeyId: gkey, - Permissions: *garage.NewAllowBucketKeyRequestPermissions(true, true, true), + Permissions: *garage.NewAllowBucketKeyRequestPermissions(read, write, owner), } binfo, _, err := client.BucketApi.AllowBucketKey(ctx).AllowBucketKeyRequest(ar).Execute() if err != nil { diff --git a/login.go b/login.go index 4bbcd65..a2c7d8f 100644 --- a/login.go +++ b/login.go @@ -143,6 +143,7 @@ func NewCapabilities(login *LoginStatus, entry *ldap.Entry) *Capabilities { // --- Logged User --- type LoggedUser struct { + Username string Login *LoginStatus Entry *ldap.Entry Capabilities *Capabilities @@ -186,7 +187,9 @@ func NewLoggedUser(login *LoginStatus) (*LoggedUser, error) { } entry := sr.Entries[0] + username := login.Info.Username lu := &LoggedUser{ + Username: username, Login: login, Entry: entry, Capabilities: NewCapabilities(login, entry), @@ -204,6 +207,7 @@ func (lu *LoggedUser) WelcomeName() string { } return ret } + func (lu *LoggedUser) S3KeyInfo() (*garage.KeyInfo, error) { var err error var keyPair *garage.KeyInfo @@ -212,7 +216,7 @@ func (lu *LoggedUser) S3KeyInfo() (*garage.KeyInfo, error) { keyID := lu.Entry.GetAttributeValue("garage_s3_access_key") if keyID == "" { // If there is no S3Key in LDAP, generate it... - keyPair, err = grgCreateKey(lu.Login.Info.Username) + keyPair, err = grgCreateKey(lu.Username) if err != nil { return nil, err } diff --git a/website.go b/website.go index ae4ffff..e581780 100644 --- a/website.go +++ b/website.go @@ -3,6 +3,7 @@ package main import ( "fmt" garage "git.deuxfleurs.fr/garage-sdk/garage-admin-sdk-golang" + "log" "sort" "strings" ) @@ -20,6 +21,7 @@ var ( ErrCantChangeVhost = fmt.Errorf("Can't change the vhost to the desired value. Maybe it's already used by someone else or an internal error occured") ErrCantRemoveOldVhost = fmt.Errorf("The new vhost is bound to the bucket but the old one can't be removed, it's an internal error") ErrFetchDedicatedKey = fmt.Errorf("Bucket has no dedicated key while it's required, it's an internal error") + ErrDedicatedKeyInvariant = fmt.Errorf("A security invariant on the dedicated key has been violated, aborting.") ) type WebsiteId struct { @@ -50,8 +52,17 @@ func NewWebsiteIdFromBucketInfo(binfo *garage.BucketInfo) *WebsiteId { return NewWebsiteId(*binfo.Id, binfo.GlobalAliases) } +// ----- + +type WebsiteDescribe struct { + AllowedWebsites *QuotaStat `json:"quota_website_count"` + BurstBucketQuotaSize string `json:"burst_bucket_quota_size"` + Websites []*WebsiteId `json:"vhosts"` +} + type WebsiteController struct { User *LoggedUser + RootKey *garage.KeyInfo WebsiteIdx map[string]*WebsiteId PrettyList []string WebsiteCount QuotaStat @@ -78,15 +89,86 @@ func NewWebsiteController(user *LoggedUser) (*WebsiteController, error) { maxW := user.Quota.WebsiteCount quota := NewQuotaStat(int64(len(wlist)), maxW, true) - return &WebsiteController{user, idx, wlist, quota}, nil + return &WebsiteController{user, keyInfo, idx, wlist, quota}, nil } -type WebsiteDescribe struct { - AllowedWebsites *QuotaStat `json:"quota_website_count"` - BurstBucketQuotaSize string `json:"burst_bucket_quota_size"` - Websites []*WebsiteId `json:"vhosts"` +func (w *WebsiteController) getDedicatedWebsiteKey(binfo *garage.BucketInfo) (*garage.KeyInfo, error) { + // Check bucket info is not null + if binfo == nil { + return nil, ErrFetchBucketInfo + } + + // Check the bucket is owned by the user's root key + usersRootKeyFound := false + for _, bucketKeyInfo := range binfo.Keys { + if *bucketKeyInfo.AccessKeyId == *w.RootKey.AccessKeyId && *bucketKeyInfo.Permissions.Owner { + usersRootKeyFound = true + break + } + } + if !usersRootKeyFound { + log.Printf("%s is not an owner of bucket %s. Invariant violated.\n", w.User.Username, *binfo.Id) + return nil, ErrDedicatedKeyInvariant + } + + // Check that username does not contain a ":" (should not be possible due to the invitation regex) + // We do this check as ":" is used as a separator + if strings.Contains(w.User.Username, ":") || w.User.Username == "" || *binfo.Id == "" { + log.Printf("Username (%s) or bucket identifier (%s) is invalid. Invariant violated.\n", w.User.Username, *binfo.Id) + return nil, ErrDedicatedKeyInvariant + } + + // Build the string template by concatening the username and the bucket identifier + dedicatedKeyName := fmt.Sprintf("%s:web:%s", w.User.Username, *binfo.Id) + + // Try to fetch the dedicated key + keyInfo, err := grgSearchKey(dedicatedKeyName) + if err != nil { + // On error, try to create it. + // @FIXME we should try to create only on 404 Not Found errors + keyInfo, err = grgCreateKey(dedicatedKeyName) + if err != nil { + // On error again, abort + return nil, err + } + log.Printf("Created dedicated key %s\n", dedicatedKeyName) + } + + // Check that the dedicated key does not contain any other bucket than this one + // and that this bucket key is found with correct permissions + permissionsOk := false + for _, buck := range keyInfo.Buckets { + if *buck.Id != *binfo.Id { + log.Printf("Key %s is used on bucket %s while it should be exclusive to %s. Invariant violated.\n", dedicatedKeyName, *buck.Id, *binfo.Id) + return nil, ErrDedicatedKeyInvariant + } + if *buck.Id == *binfo.Id && *buck.Permissions.Read && *buck.Permissions.Write { + permissionsOk = true + } + } + + // Allow this bucket on the key if it's not already the case + // (will be executed when 1) key is first created and 2) as an healing mechanism) + if !permissionsOk { + binfo, err = grgAllowKeyOnBucket(*binfo.Id, *keyInfo.AccessKeyId, true, true, false) + if err != nil { + return nil, err + } + log.Printf("Key %s was not properly allowed on bucket %s, fixing permissions. Intended behavior.", dedicatedKeyName, *binfo.Id) + + // Refresh the key to have an object with proper permissions + keyInfo, err = grgGetKey(*keyInfo.AccessKeyId) + if err != nil { + return nil, err + } + } + + // Return the key + return keyInfo, nil } +//@TODO: flushDedicatedWebsiteKey() + func (w *WebsiteController) Describe() (*WebsiteDescribe, error) { r := make([]*WebsiteId, 0, len(w.PrettyList)) for _, k := range w.PrettyList { @@ -111,9 +193,12 @@ func (w *WebsiteController) Inspect(pretty string) (*WebsiteView, error) { return nil, ErrFetchBucketInfo } - // @TODO: fetch the associated key + dedicatedKey, err := w.getDedicatedWebsiteKey(binfo) + if err != nil { + return nil, err + } - return NewWebsiteView(binfo, nil) + return NewWebsiteView(binfo, dedicatedKey) } func (w *WebsiteController) Patch(pretty string, patch *WebsitePatch) (*WebsiteView, error) { @@ -158,10 +243,15 @@ func (w *WebsiteController) Patch(pretty string, patch *WebsitePatch) (*WebsiteV } if patch.RotateKey != nil && *patch.RotateKey { - // @TODO: rotate key + // @TODO: rotate key by calling flush } - return NewWebsiteView(binfo, nil) + dedicatedKey, err := w.getDedicatedWebsiteKey(binfo) + if err != nil { + return nil, err + } + + return NewWebsiteView(binfo, dedicatedKey) } func (w *WebsiteController) Create(pretty string) (*WebsiteView, error) { @@ -185,7 +275,7 @@ func (w *WebsiteController) Create(pretty string) (*WebsiteView, error) { return nil, err } - binfo, err = grgAllowKeyOnBucket(*binfo.Id, *s3key.AccessKeyId) + binfo, err = grgAllowKeyOnBucket(*binfo.Id, *s3key.AccessKeyId, true, true, true) if err != nil { return nil, ErrCantAllowKey } @@ -204,9 +294,12 @@ func (w *WebsiteController) Create(pretty string) (*WebsiteView, error) { } // Create a dedicated key - // @TODO + dedicatedKey, err := w.getDedicatedWebsiteKey(binfo) + if err != nil { + return nil, err + } - return NewWebsiteView(binfo, nil) + return NewWebsiteView(binfo, dedicatedKey) } func (w *WebsiteController) Delete(pretty string) error { @@ -234,7 +327,7 @@ func (w *WebsiteController) Delete(pretty string) error { } // Delete dedicated key - // @TODO + // @TODO call flush // Actually delete bucket err = grgDeleteBucket(website.Internal) @@ -251,7 +344,7 @@ type WebsiteView struct { func NewWebsiteView(binfo *garage.BucketInfo, s3key *garage.KeyInfo) (*WebsiteView, error) { if binfo == nil { - return nil, ErrFetchBucketInfo + return nil, ErrFetchBucketInfo } if s3key == nil { return nil, ErrFetchDedicatedKey @@ -263,16 +356,16 @@ func NewWebsiteView(binfo *garage.BucketInfo, s3key *garage.KeyInfo) (*WebsiteVi size := NewQuotaStat(*binfo.Bytes, (&q).GetMaxSize(), true) objects := NewQuotaStat(*binfo.Objects, (&q).GetMaxObjects(), false) return &WebsiteView{ - wid, + wid, *s3key.AccessKeyId, *s3key.SecretAccessKey.Get(), - size, + size, objects, }, nil } type WebsitePatch struct { - Size *int64 `json:"quota_size"` - Vhost *string `json:"vhost"` - RotateKey *bool `json:"rotate_key"` + Size *int64 `json:"quota_size"` + Vhost *string `json:"vhost"` + RotateKey *bool `json:"rotate_key"` } diff --git a/webui_website.go b/webui_website.go index 9685374..642c837 100644 --- a/webui_website.go +++ b/webui_website.go @@ -104,7 +104,7 @@ func handleWebsiteInspect(w http.ResponseWriter, r *http.Request) { } case "rotate_key": do_action := true - _, processErr = ctrl.Patch(bucketName, &WebsitePatch { RotateKey: &do_action }) + _, processErr = ctrl.Patch(bucketName, &WebsitePatch{RotateKey: &do_action}) default: processErr = fmt.Errorf("Unknown action") } From df79d110285f1956f63206e7bc1f29e49dd6f088 Mon Sep 17 00:00:00 2001 From: Quentin Dufour Date: Mon, 24 Jun 2024 10:43:11 +0200 Subject: [PATCH 6/9] implement flush for dedicated key, allow delete & key rotation --- garage.go | 11 +++++++++ website.go | 70 ++++++++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 77 insertions(+), 4 deletions(-) diff --git a/garage.go b/garage.go index fabd6bf..44b8dae 100644 --- a/garage.go +++ b/garage.go @@ -55,6 +55,17 @@ func grgSearchKey(name string) (*garage.KeyInfo, error) { return resp, nil } +func grgDelKey(accessKey string) error { + client, ctx := gadmin() + + _, err := client.KeyApi.DeleteKey(ctx).Id(accessKey).Execute() + if err != nil { + fmt.Printf("%+v\n", err) + return err + } + return nil +} + func grgCreateBucket(bucket string) (*garage.BucketInfo, error) { client, ctx := gadmin() diff --git a/website.go b/website.go index e581780..d0bed9c 100644 --- a/website.go +++ b/website.go @@ -134,8 +134,14 @@ func (w *WebsiteController) getDedicatedWebsiteKey(binfo *garage.BucketInfo) (*g log.Printf("Created dedicated key %s\n", dedicatedKeyName) } + // Check that the key name is *exactly* the one we requested + if *keyInfo.Name != dedicatedKeyName { + log.Printf("Expected key: %s, got %s. Invariant violated.\n", dedicatedKeyName, *keyInfo.Name) + return nil, ErrDedicatedKeyInvariant + } + // Check that the dedicated key does not contain any other bucket than this one - // and that this bucket key is found with correct permissions + // and report if this bucket key is found with correct permissions permissionsOk := false for _, buck := range keyInfo.Buckets { if *buck.Id != *binfo.Id { @@ -167,7 +173,57 @@ func (w *WebsiteController) getDedicatedWebsiteKey(binfo *garage.BucketInfo) (*g return keyInfo, nil } -//@TODO: flushDedicatedWebsiteKey() +func (w *WebsiteController) flushDedicatedWebsiteKey(binfo *garage.BucketInfo) error { + // Check bucket info is not null + if binfo == nil { + return ErrFetchBucketInfo + } + + // Check the bucket is owned by the user's root key + usersRootKeyFound := false + for _, bucketKeyInfo := range binfo.Keys { + if *bucketKeyInfo.AccessKeyId == *w.RootKey.AccessKeyId && *bucketKeyInfo.Permissions.Owner { + usersRootKeyFound = true + break + } + } + if !usersRootKeyFound { + log.Printf("%s is not an owner of bucket %s. Invariant violated.\n", w.User.Username, *binfo.Id) + return ErrDedicatedKeyInvariant + } + + // Build the string template by concatening the username and the bucket identifier + dedicatedKeyName := fmt.Sprintf("%s:web:%s", w.User.Username, *binfo.Id) + + // Fetch the dedicated key + keyInfo, err := grgSearchKey(dedicatedKeyName) + if err != nil { + return err + } + + // Check that the key name is *exactly* the one we requested + if *keyInfo.Name != dedicatedKeyName { + log.Printf("Expected key: %s, got %s. Invariant violated.\n", dedicatedKeyName, *keyInfo.Name) + return ErrDedicatedKeyInvariant + } + + // Check that the dedicated key contains no other bucket than this one + // (can also be empty, useful to heal a partially created key) + for _, buck := range keyInfo.Buckets { + if *buck.Id != *binfo.Id { + log.Printf("Key %s is used on bucket %s while it should be exclusive to %s. Invariant violated.\n", dedicatedKeyName, *buck.Id, *binfo.Id) + return ErrDedicatedKeyInvariant + } + } + + // Finally delete this key + err = grgDelKey(*keyInfo.AccessKeyId) + if err != nil { + return err + } + log.Printf("Deleted dedicated key %s", dedicatedKeyName) + return nil +} func (w *WebsiteController) Describe() (*WebsiteDescribe, error) { r := make([]*WebsiteId, 0, len(w.PrettyList)) @@ -243,7 +299,10 @@ func (w *WebsiteController) Patch(pretty string, patch *WebsitePatch) (*WebsiteV } if patch.RotateKey != nil && *patch.RotateKey { - // @TODO: rotate key by calling flush + err = w.flushDedicatedWebsiteKey(binfo) + if err != nil { + return nil, err + } } dedicatedKey, err := w.getDedicatedWebsiteKey(binfo) @@ -327,7 +386,10 @@ func (w *WebsiteController) Delete(pretty string) error { } // Delete dedicated key - // @TODO call flush + err = w.flushDedicatedWebsiteKey(binfo) + if err != nil { + return err + } // Actually delete bucket err = grgDeleteBucket(website.Internal) From 64363b29e062f65022bc422c1a8b7d00727c254a Mon Sep 17 00:00:00 2001 From: Quentin Dufour Date: Mon, 24 Jun 2024 10:50:17 +0200 Subject: [PATCH 7/9] update gomod2nix (bump garage sdk bis) --- gomod2nix.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gomod2nix.toml b/gomod2nix.toml index 4f10838..21b9dbc 100644 --- a/gomod2nix.toml +++ b/gomod2nix.toml @@ -2,8 +2,8 @@ schema = 3 [mod] [mod."git.deuxfleurs.fr/garage-sdk/garage-admin-sdk-golang"] - version = "v0.0.0-20230131081355-c965fe7f7dc9" - hash = "sha256-qJN9yDMIh3xRk/3IYEWZca/biMVXXmDlPTzy0Cg11oc=" + version = "v0.0.0-20231128153612-8b81fae65e5e" + hash = "sha256-o9kbcJ25/cYYwWZz/LBF7ZDyW8bZAjdg5pPu0gvb5JQ=" [mod."github.com/emersion/go-sasl"] version = "v0.0.0-20191210011802-430746ea8b9b" hash = "sha256-bADpAn0ZhlTTsEB3MsG8J31cQjTtHTzohX/wkL1aMIc=" From 898122c19ecba8b7e2abaa8472feb62304e273b5 Mon Sep 17 00:00:00 2001 From: Quentin Dufour Date: Mon, 24 Jun 2024 12:07:28 +0200 Subject: [PATCH 8/9] rework inspect page --- templates/garage_website_inspect.html | 279 +++++++++++++++++++++----- website.go | 2 + 2 files changed, 236 insertions(+), 45 deletions(-) diff --git a/templates/garage_website_inspect.html b/templates/garage_website_inspect.html index af87955..56979ed 100644 --- a/templates/garage_website_inspect.html +++ b/templates/garage_website_inspect.html @@ -3,8 +3,8 @@ {{define "body"}}
@@ -15,9 +15,9 @@ {{ end }}
- + - + Nouveau site web @@ -45,11 +45,13 @@

{{ .View.Name.Url }}

+ +
Quotas
{{ .View.Size.Percent }}% -
+

@@ -59,57 +61,244 @@ {{ end }}

-
Informations de connexion
- - - - - - - - - - - - - - - - - - - - - - - - - - - -
Identifiant de clé{{ .View.AccessKeyId }}
Clé secrète - Cliquer pour afficher la clé secrète - -
Régiongarage
Endpoint URLhttps://garage.deuxfleurs.fr
Type d'URLDNS et chemin (préférer chemin)
SignatureVersion 4
- +
Actions
- - + + Changer le nom de domaine - +
- {{ if .View.Name.Expanded }} -
Vous ne savez pas comment configurer votre nom de domaine ?
-

Le nom de domaine {{ .View.Name.Url }} n'est pas géré par Deuxfleurs, il vous revient donc de configurer la zone DNS. Vous devez ajouter une entrée CNAME garage.deuxfleurs.fr ou ALIAS garage.deuxfleurs.fr auprès de votre hébergeur DNS, qui est souvent aussi le bureau d'enregistrement (eg. Gandi, GoDaddy, BookMyName, etc.).

- {{ end }} + +
Informations de connexion
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
Identifiant de clé{{ .View.AccessKeyId }}
Clé secrète + [Afficher la clé secrète] + +
Régiongarage
Endpoint URLhttps://garage.deuxfleurs.fr
Type d'URLDNS et chemin (préférer chemin)
SignatureVersion 4
+ +

Configurer votre logiciel :

+ +
+
+
+

+ +

+
+
+
+

Lancez la commande :

+
aws --profile {{ .View.Name.Pretty }} configure
+ +

Entrez les informations suivantes quand elles vous sont demandées :

+
+
AWS Access Key ID [None]:
{{ .View.AccessKeyId }}
+
AWS Secret Access Key [None]:
[Afficher la clé secrète]
+
Default region name [None]:
garage
+
Default output format [None]:
(laissez vide et appuyez sur entrée)
+
+ +

Finalisez la configuration :

+
aws --profile {{ .View.Name.Pretty }} configure set endpoint_url https://garage.deuxfleurs.fr
+

Pour déployer votre dossier local public lancez :

+
+aws --profile {{ .View.Name.Pretty }} s3 sync ./public s3://{{ .View.Name.Pretty }}
+								
+
+
+
+ +
+
+

+ +

+
+ +
+
+

Vous pouvez configurer Minio CLI avec cette commande :

+
+mc alias set \
+  {{ .View.Name.Pretty }} \
+  https://garage.deuxfleurs.fr \
+  {{ .View.AccessKeyId }} \
+  [Afficher la clé secrète] \
+  --api S3v4
+								
+

Et ensuite copiez votre site web avec la sous-commande mirror de Minio CLI :

+
+mc mirror --overwrite ./public/ {{ .View.Name.Pretty }}/
+								
+
+
+
+ +
+
+

+ +

+
+
+
+

Créez un fichier nommé .deployment.secrets (ne commitez pas ce fichier dans votre dépôt !) :

+
+export AWS_ACCESS_KEY_ID={{ .View.AccessKeyId }}
+export AWS_SECRET_ACCESS_KEY=[Afficher la clé secrète]
+								
+

Dans votre fichier de configuration Hugo config.toml (que vous pouvez commiter), rajoutez :

+
+[[deployment.targets]]
+ URL = "s3://bucket?endpoint=garage.deuxfleurs.fr&s3ForcePathStyle=true&region=garage"
+								
+ +

Pour déployer, sourcez le fichier de configuration et laissez hugo faire :

+
+source .deployment.secrets
+hugo deploy
+								
+
+
+
+
+
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + +
Nom d'utilisateur-ice{{ .Describe.Username }}
Mot de passe(votre mot de passe guichet)
Hôtesftp://sftp.deuxfleurs.fr
Port2222
+

Configurez votre logiciel :

+ +
+
+
+

+ +

+
+
+
+

Déployer le dossier local public sur le site web {{ .View.Name.Pretty }} :

+
+scp -oHostKeyAlgorithms=+ssh-rsa -P2222 -r ./public {{ .Describe.Username }}@sftp.deuxfleurs.fr:{{ .View.Name.Pretty }}/
+								
+
+
+
+
+
+

+ +

+
+
+
+

Dans la barre de connexion rapide du haut, entrez :

+
+
Hôte
sftp://sftp.deuxfleurs.fr
+
Nom d'utilisateur
{{ .Describe.Username }}
+
Mot de passe
(votre mot de passe guichet)
+
Port
2222
+
+

Cliquez ensuite sur Connexion rapide

+
+
+
+
+
+
+ + {{ if .View.Name.Expanded }} +
Vous ne savez pas comment configurer votre nom de domaine ?
+

Le nom de domaine {{ .View.Name.Url }} n'est pas géré par Deuxfleurs, il vous revient donc de configurer la zone DNS. Vous devez ajouter une entrée CNAME garage.deuxfleurs.fr ou ALIAS garage.deuxfleurs.fr auprès de votre hébergeur DNS, qui est souvent aussi le bureau d'enregistrement (eg. Gandi, GoDaddy, BookMyName, etc.).

+ {{ end }} + +
{{ end }} diff --git a/website.go b/website.go index d0bed9c..ed39d28 100644 --- a/website.go +++ b/website.go @@ -55,6 +55,7 @@ func NewWebsiteIdFromBucketInfo(binfo *garage.BucketInfo) *WebsiteId { // ----- type WebsiteDescribe struct { + Username string `json:"username"` AllowedWebsites *QuotaStat `json:"quota_website_count"` BurstBucketQuotaSize string `json:"burst_bucket_quota_size"` Websites []*WebsiteId `json:"vhosts"` @@ -232,6 +233,7 @@ func (w *WebsiteController) Describe() (*WebsiteDescribe, error) { } return &WebsiteDescribe{ + w.User.Username, &w.WebsiteCount, w.User.Quota.WebsiteSizeBurstedPretty(), r, From bc7bc61f7449b1f41ed9eb46388ab0c149856f96 Mon Sep 17 00:00:00 2001 From: Quentin Dufour Date: Mon, 24 Jun 2024 12:12:39 +0200 Subject: [PATCH 9/9] Ajout de WebDAV --- templates/garage_website_inspect.html | 53 ++++++++++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/templates/garage_website_inspect.html b/templates/garage_website_inspect.html index 56979ed..6182f0d 100644 --- a/templates/garage_website_inspect.html +++ b/templates/garage_website_inspect.html @@ -131,7 +131,7 @@
-
+

Lancez la commande :

aws --profile {{ .View.Name.Pretty }} configure
@@ -290,6 +290,57 @@ scp -oHostKeyAlgorithms=+ssh-rsa -P2222 -r ./public {{ .Describe.Username }}@sft
+ +
+
+ + + + + + + + + + + + + + + + + + + + + +
Nom d'utilisateur-ice{{ .Describe.Username }}
Mot de passe(votre mot de passe guichet)
Hôtehttps://bagage.deuxfleurs.fr ou davs://bagage.deuxfleurs.fr
Port443 (par défaut)
+

Configurez votre logiciel :

+ +
+
+
+

+ +

+
+
+
+

Vous pouvez naviguer dans vos fichiers via l'explorateur web. + Utilisez simplement vos identifiants Guichet, l'explorateur est préconfiguré.

+ +

Accéder à l'explorateur

+
+
+
+
+
+