mirror of
https://github.com/datarhei/core.git
synced 2025-10-03 23:26:39 +08:00
414 lines
12 KiB
Go
414 lines
12 KiB
Go
package api
|
|
|
|
import (
|
|
"errors"
|
|
"net/http"
|
|
|
|
"github.com/datarhei/core/v16/cluster/store"
|
|
"github.com/datarhei/core/v16/http/api"
|
|
"github.com/datarhei/core/v16/http/handler/util"
|
|
"github.com/datarhei/core/v16/iam/identity"
|
|
"github.com/datarhei/core/v16/iam/policy"
|
|
"github.com/labstack/echo/v4"
|
|
)
|
|
|
|
// Add adds a new identity to the cluster
|
|
// @Summary Add a new identiy
|
|
// @Description Add a new identity
|
|
// @Tags v16.?.?
|
|
// @ID cluster-3-add-identity
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param config body api.IAMUser true "Identity"
|
|
// @Success 200 {object} api.IAMUser
|
|
// @Failure 400 {object} api.Error
|
|
// @Failure 403 {object} api.Error
|
|
// @Security ApiKeyAuth
|
|
// @Router /api/v3/cluster/iam/user [post]
|
|
func (h *ClusterHandler) IAMIdentityAdd(c echo.Context) error {
|
|
ctxuser := util.DefaultContext(c, "user", "")
|
|
superuser := util.DefaultContext(c, "superuser", false)
|
|
domain := util.DefaultQuery(c, "domain", "")
|
|
|
|
user := api.IAMUser{}
|
|
|
|
if err := util.ShouldBindJSON(c, &user); err != nil {
|
|
return api.Err(http.StatusBadRequest, "", "invalid JSON: %s", err.Error())
|
|
}
|
|
|
|
iamuser, iampolicies := user.Unmarshal()
|
|
|
|
if !h.iam.Enforce(ctxuser, domain, "iam", iamuser.Name, "write") {
|
|
return api.Err(http.StatusForbidden, "", "Not allowed to create user '%s'", iamuser.Name)
|
|
}
|
|
|
|
for _, p := range iampolicies {
|
|
if !h.iam.Enforce(ctxuser, p.Domain, "iam", iamuser.Name, "write") {
|
|
return api.Err(http.StatusForbidden, "", "Not allowed to write policy: %v", p)
|
|
}
|
|
}
|
|
|
|
if !superuser && iamuser.Superuser {
|
|
return api.Err(http.StatusForbidden, "", "Only superusers can add superusers")
|
|
}
|
|
|
|
if err := h.cluster.IAMIdentityAdd("", iamuser); err != nil {
|
|
return api.Err(http.StatusBadRequest, "", "invalid identity: %s", err.Error())
|
|
}
|
|
|
|
if err := h.cluster.IAMPoliciesSet("", iamuser.Name, iampolicies); err != nil {
|
|
return api.Err(http.StatusBadRequest, "", "Invalid policies: %s", err.Error())
|
|
}
|
|
|
|
return c.JSON(http.StatusOK, user)
|
|
}
|
|
|
|
// IAMIdentityUpdate replaces an existing user
|
|
// @Summary Replace an existing user
|
|
// @Description Replace an existing user.
|
|
// @Tags v16.?.?
|
|
// @ID cluster-3-update-identity
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param name path string true "Username"
|
|
// @Param domain query string false "Domain of the acting user"
|
|
// @Param user body api.IAMUser true "User definition"
|
|
// @Success 200 {object} api.IAMUser
|
|
// @Failure 400 {object} api.Error
|
|
// @Failure 403 {object} api.Error
|
|
// @Failure 404 {object} api.Error
|
|
// @Failure 500 {object} api.Error
|
|
// @Security ApiKeyAuth
|
|
// @Router /api/v3/cluster/iam/user/{name} [put]
|
|
func (h *ClusterHandler) IAMIdentityUpdate(c echo.Context) error {
|
|
ctxuser := util.DefaultContext(c, "user", "")
|
|
superuser := util.DefaultContext(c, "superuser", false)
|
|
domain := util.DefaultQuery(c, "domain", "")
|
|
name := util.PathParam(c, "name")
|
|
|
|
if !h.iam.Enforce(ctxuser, domain, "iam", name, "write") {
|
|
return api.Err(http.StatusForbidden, "", "not allowed to modify this user")
|
|
}
|
|
|
|
var iamuser identity.User
|
|
var err error
|
|
|
|
if name != "$anon" {
|
|
iamuser, err = h.iam.GetIdentity(name)
|
|
if err != nil {
|
|
return api.Err(http.StatusNotFound, "", "user not found: %s", err.Error())
|
|
}
|
|
} else {
|
|
iamuser = identity.User{
|
|
Name: "$anon",
|
|
}
|
|
}
|
|
|
|
iampolicies := h.iam.ListPolicies(name, "", nil, "", nil)
|
|
|
|
user := api.IAMUser{}
|
|
user.Marshal(iamuser, iampolicies)
|
|
|
|
if err := util.ShouldBindJSON(c, &user); err != nil {
|
|
return api.Err(http.StatusBadRequest, "", "invalid JSON: %s", err.Error())
|
|
}
|
|
|
|
iamuser, iampolicies = user.Unmarshal()
|
|
|
|
if !h.iam.Enforce(ctxuser, domain, "iam", iamuser.Name, "write") {
|
|
return api.Err(http.StatusForbidden, "", "not allowed to create user '%s'", iamuser.Name)
|
|
}
|
|
|
|
for _, p := range iampolicies {
|
|
if !h.iam.Enforce(ctxuser, p.Domain, "iam", iamuser.Name, "write") {
|
|
return api.Err(http.StatusForbidden, "", "not allowed to write policy: %v", p)
|
|
}
|
|
}
|
|
|
|
if !superuser && iamuser.Superuser {
|
|
return api.Err(http.StatusForbidden, "", "only superusers can modify superusers")
|
|
}
|
|
|
|
if name != "$anon" {
|
|
err = h.cluster.IAMIdentityUpdate("", name, iamuser)
|
|
if err != nil {
|
|
return api.Err(http.StatusBadRequest, "", "%s", err.Error())
|
|
}
|
|
}
|
|
|
|
err = h.cluster.IAMPoliciesSet("", iamuser.Name, iampolicies)
|
|
if err != nil {
|
|
return api.Err(http.StatusInternalServerError, "", "set policies: %s", err.Error())
|
|
}
|
|
|
|
return c.JSON(http.StatusOK, user)
|
|
}
|
|
|
|
// IAMIdentityUpdatePolicies replaces existing user policies
|
|
// @Summary Replace policies of an user
|
|
// @Description Replace policies of an user
|
|
// @Tags v16.?.?
|
|
// @ID cluster-3-update-user-policies
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param name path string true "Username"
|
|
// @Param domain query string false "Domain of the acting user"
|
|
// @Param user body []api.IAMPolicy true "Policy definitions"
|
|
// @Success 200 {array} api.IAMPolicy
|
|
// @Failure 400 {object} api.Error
|
|
// @Failure 403 {object} api.Error
|
|
// @Failure 404 {object} api.Error
|
|
// @Failure 500 {object} api.Error
|
|
// @Security ApiKeyAuth
|
|
// @Router /api/v3/cluster/iam/user/{name}/policy [put]
|
|
func (h *ClusterHandler) IAMIdentityUpdatePolicies(c echo.Context) error {
|
|
ctxuser := util.DefaultContext(c, "user", "")
|
|
superuser := util.DefaultContext(c, "superuser", false)
|
|
domain := util.DefaultQuery(c, "domain", "")
|
|
name := util.PathParam(c, "name")
|
|
|
|
if !h.iam.Enforce(ctxuser, domain, "iam", name, "write") {
|
|
return api.Err(http.StatusForbidden, "", "not allowed to modify this user")
|
|
}
|
|
|
|
var iamuser identity.User
|
|
var err error
|
|
|
|
if name != "$anon" {
|
|
iamuser, err = h.iam.GetIdentity(name)
|
|
if err != nil {
|
|
return api.Err(http.StatusNotFound, "", "unknown identity: %s", err.Error())
|
|
}
|
|
} else {
|
|
iamuser = identity.User{
|
|
Name: "$anon",
|
|
}
|
|
}
|
|
|
|
policies := []api.IAMPolicy{}
|
|
|
|
if err := util.ShouldBindJSONValidation(c, &policies, false); err != nil {
|
|
return api.Err(http.StatusBadRequest, "", "invalid JSON: %s", err.Error())
|
|
}
|
|
|
|
for _, p := range policies {
|
|
err := c.Validate(p)
|
|
if err != nil {
|
|
return api.Err(http.StatusBadRequest, "", "invalid JSON: %s", err.Error())
|
|
}
|
|
}
|
|
|
|
accessPolicies := []policy.Policy{}
|
|
|
|
for _, p := range policies {
|
|
if !h.iam.Enforce(ctxuser, p.Domain, "iam", iamuser.Name, "write") {
|
|
return api.Err(http.StatusForbidden, "", "not allowed to write policy: %v", p)
|
|
}
|
|
|
|
accessPolicies = append(accessPolicies, policy.Policy{
|
|
Name: name,
|
|
Domain: p.Domain,
|
|
Types: p.Types,
|
|
Resource: p.Resource,
|
|
Actions: p.Actions,
|
|
})
|
|
}
|
|
|
|
if !superuser && iamuser.Superuser {
|
|
return api.Err(http.StatusForbidden, "", "only superusers can modify superusers")
|
|
}
|
|
|
|
err = h.cluster.IAMPoliciesSet("", name, accessPolicies)
|
|
if err != nil {
|
|
if errors.Is(err, store.ErrNotFound) {
|
|
return api.Err(http.StatusNotFound, "", "set policies: %s", err.Error())
|
|
}
|
|
return api.Err(http.StatusInternalServerError, "", "set policies: %s", err.Error())
|
|
}
|
|
|
|
return c.JSON(http.StatusOK, policies)
|
|
}
|
|
|
|
// IAMReload reloads the identities and policies from the cluster store to IAM
|
|
// @Summary Reload identities and policies
|
|
// @Description Reload identities and policies
|
|
// @Tags v16.?.?
|
|
// @ID cluster-3-iam-reload
|
|
// @Produce json
|
|
// @Success 200 {string} string
|
|
// @Success 500 {object} api.Error
|
|
// @Security ApiKeyAuth
|
|
// @Router /api/v3/cluster/iam/reload [get]
|
|
func (h *ClusterHandler) IAMReload(c echo.Context) error {
|
|
err := h.iam.ReloadIndentities()
|
|
if err != nil {
|
|
return api.Err(http.StatusInternalServerError, "", "reload identities: %w", err.Error())
|
|
}
|
|
|
|
err = h.iam.ReloadPolicies()
|
|
if err != nil {
|
|
return api.Err(http.StatusInternalServerError, "", "reload policies: %w", err.Error())
|
|
}
|
|
|
|
return c.JSON(http.StatusOK, "OK")
|
|
}
|
|
|
|
// IAMIdentityList returns the list of identities stored in IAM
|
|
// @Summary List of identities in IAM
|
|
// @Description List of identities in IAM
|
|
// @Tags v16.?.?
|
|
// @ID cluster-3-iam-list-identities
|
|
// @Produce json
|
|
// @Success 200 {array} api.IAMUser
|
|
// @Security ApiKeyAuth
|
|
// @Router /api/v3/cluster/iam/user [get]
|
|
func (h *ClusterHandler) IAMIdentityList(c echo.Context) error {
|
|
ctxuser := util.DefaultContext(c, "user", "")
|
|
domain := util.DefaultQuery(c, "domain", "")
|
|
|
|
identities := h.iam.ListIdentities()
|
|
|
|
users := make([]api.IAMUser, len(identities)+1)
|
|
|
|
for i, iamuser := range identities {
|
|
if !h.iam.Enforce(ctxuser, domain, "iam", iamuser.Name, "read") {
|
|
continue
|
|
}
|
|
|
|
if !h.iam.Enforce(ctxuser, domain, "iam", iamuser.Name, "write") {
|
|
iamuser = identity.User{
|
|
Name: iamuser.Name,
|
|
}
|
|
}
|
|
|
|
policies := h.iam.ListPolicies(iamuser.Name, "", nil, "", nil)
|
|
|
|
users[i].Marshal(iamuser, policies)
|
|
}
|
|
|
|
anon := identity.User{
|
|
Name: "$anon",
|
|
}
|
|
|
|
policies := h.iam.ListPolicies("$anon", "", nil, "", nil)
|
|
|
|
users[len(users)-1].Marshal(anon, policies)
|
|
|
|
return c.JSON(http.StatusOK, users)
|
|
}
|
|
|
|
// IAMIdentityGet returns the identity stored in IAM
|
|
// @Summary Identity in IAM
|
|
// @Description Identity in IAM
|
|
// @Tags v16.?.?
|
|
// @ID cluster-3-iam-list-identity
|
|
// @Produce json
|
|
// @Success 200 {object} api.IAMUser
|
|
// @Failure 403 {object} api.Error
|
|
// @Failure 404 {object} api.Error
|
|
// @Security ApiKeyAuth
|
|
// @Router /api/v3/cluster/iam/user/{name} [get]
|
|
func (h *ClusterHandler) IAMIdentityGet(c echo.Context) error {
|
|
ctxuser := util.DefaultContext(c, "user", "")
|
|
domain := util.DefaultQuery(c, "domain", "")
|
|
name := util.PathParam(c, "name")
|
|
|
|
if !h.iam.Enforce(ctxuser, domain, "iam", name, "read") {
|
|
return api.Err(http.StatusForbidden, "", "Not allowed to access this user")
|
|
}
|
|
|
|
var iamuser identity.User
|
|
var err error
|
|
|
|
if name != "$anon" {
|
|
iamuser, err = h.iam.GetIdentity(name)
|
|
if err != nil {
|
|
return api.Err(http.StatusNotFound, "", "%s", err.Error())
|
|
}
|
|
|
|
if ctxuser != iamuser.Name {
|
|
if !h.iam.Enforce(ctxuser, domain, "iam", name, "write") {
|
|
iamuser = identity.User{
|
|
Name: iamuser.Name,
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
iamuser = identity.User{
|
|
Name: "$anon",
|
|
}
|
|
}
|
|
|
|
iampolicies := h.iam.ListPolicies(iamuser.Name, "", nil, "", nil)
|
|
|
|
user := api.IAMUser{}
|
|
user.Marshal(iamuser, iampolicies)
|
|
|
|
return c.JSON(http.StatusOK, user)
|
|
}
|
|
|
|
// IAMPolicyList returns the list of policies stored in IAM
|
|
// @Summary List of policies in IAM
|
|
// @Description List of policies IAM
|
|
// @Tags v16.?.?
|
|
// @ID cluster-3-iam-list-policies
|
|
// @Produce json
|
|
// @Success 200 {array} api.IAMPolicy
|
|
// @Security ApiKeyAuth
|
|
// @Router /api/v3/cluster/iam/policies [get]
|
|
func (h *ClusterHandler) IAMPolicyList(c echo.Context) error {
|
|
iampolicies := h.iam.ListPolicies("", "", nil, "", nil)
|
|
|
|
policies := []api.IAMPolicy{}
|
|
|
|
for _, pol := range iampolicies {
|
|
policies = append(policies, api.IAMPolicy{
|
|
Name: pol.Name,
|
|
Domain: pol.Domain,
|
|
Types: pol.Types,
|
|
Resource: pol.Resource,
|
|
Actions: pol.Actions,
|
|
})
|
|
}
|
|
|
|
return c.JSON(http.StatusOK, policies)
|
|
}
|
|
|
|
// Delete deletes the identity with the given name from the cluster
|
|
// @Summary Delete an identity by its name
|
|
// @Description Delete an identity by its name
|
|
// @Tags v16.?.?
|
|
// @ID cluster-3-delete-identity
|
|
// @Produce json
|
|
// @Param name path string true "Identity name"
|
|
// @Success 200 {string} string
|
|
// @Failure 403 {object} api.Error
|
|
// @Failure 404 {object} api.Error
|
|
// @Security ApiKeyAuth
|
|
// @Router /api/v3/cluster/iam/user/{name} [delete]
|
|
func (h *ClusterHandler) IAMIdentityRemove(c echo.Context) error {
|
|
ctxuser := util.DefaultContext(c, "user", "")
|
|
superuser := util.DefaultContext(c, "superuser", false)
|
|
domain := util.DefaultQuery(c, "domain", "$none")
|
|
name := util.PathParam(c, "name")
|
|
|
|
if !h.iam.Enforce(ctxuser, domain, "iam", name, "write") {
|
|
return api.Err(http.StatusForbidden, "", "Not allowed to delete this user")
|
|
}
|
|
|
|
iamuser, err := h.iam.GetIdentity(name)
|
|
if err != nil {
|
|
return api.Err(http.StatusNotFound, "", "%s", err.Error())
|
|
}
|
|
|
|
if !superuser && iamuser.Superuser {
|
|
return api.Err(http.StatusForbidden, "", "Only superusers can remove superusers")
|
|
}
|
|
|
|
if err := h.cluster.IAMIdentityRemove("", name); err != nil {
|
|
return api.Err(http.StatusBadRequest, "", "invalid identity: %s", err.Error())
|
|
}
|
|
|
|
return c.JSON(http.StatusOK, "OK")
|
|
}
|