api/auth.go
Rama main · réplica de lectura del repositorio original.
package api
import (
"crypto"
"crypto/hmac"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"net/http"
"slices"
"strings"
"time"
"github.com/coroot/coroot/api/forms"
"github.com/coroot/coroot/collector"
"github.com/coroot/coroot/db"
"github.com/coroot/coroot/rbac"
"github.com/coroot/coroot/utils"
"k8s.io/klog"
)
type Session struct {
Id int `json:"id"`
}
const (
AuthSecretSettingName = "auth_secret"
HashFunc = crypto.SHA256
SessionCookieName = "coroot_session"
SessionCookieTTL = 7 * 24 * time.Hour
)
func (api *Api) AuthInit(anonymousRole string, adminPassword string) error {
if anonymousRole != "" {
role := rbac.RoleName(anonymousRole)
roles, err := api.roles.GetRoles()
if err != nil {
return err
}
if !role.Valid(roles) {
var names []rbac.RoleName
for _, r := range roles {
names = append(names, r.Name)
}
return fmt.Errorf("anonymous role must one of %s, got '%s'", names, role)
}
api.authAnonymousRole = role
klog.Infoln("anonymous access enabled with the role:", role)
}
var secret string
err := api.db.GetSetting(AuthSecretSettingName, &secret)
if err != nil {
if errors.Is(err, db.ErrNotFound) {
secret = utils.RandomString(HashFunc.Size())
err = api.db.SetSetting(AuthSecretSettingName, secret)
if err != nil {
return err
}
} else {
return err
}
}
api.authSecret = secret
err = api.db.CreateAdminIfNotExists(adminPassword)
if err != nil {
return err
}
return nil
}
func (api *Api) isConfigServiceAccount(u *db.User) bool {
if !u.IsServiceAccount() {
return false
}
for _, sa := range api.cfg.Auth.ServiceAccounts {
if sa.Name == u.Email {
return true
}
}
return false
}
func (api *Api) GetUserByApiKey(r *http.Request) *db.User {
key, ok := strings.CutPrefix(r.Header.Get("Authorization"), "Bearer ")
if !ok || key == "" {
return nil
}
user, err := api.db.GetUserByApiKey(key)
if err != nil {
if !errors.Is(err, db.ErrNotFound) {
klog.Errorln(err)
}
return nil
}
return user
}
func (api *Api) Auth(h func(http.ResponseWriter, *http.Request, *db.User)) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if user := api.GetUser(r); user != nil {
h(w, r, user)
return
}
admin, err := api.db.DefaultAdminUserIsTheOnlyUser()
if err != nil {
klog.Errorln(err)
http.Error(w, "", http.StatusInternalServerError)
return
}
if admin != nil {
http.Error(w, "set_admin_password", http.StatusUnauthorized)
} else {
http.Error(w, "", http.StatusUnauthorized)
}
return
}
}
func (api *Api) getProjectByApiKey(apiKey string) (*db.Project, error) {
projects, err := api.db.GetProjects()
if err != nil {
return nil, err
}
for _, p := range projects {
if p.Multicluster() {
continue
}
for _, key := range p.Settings.ApiKeys {
if !key.IsEmpty() && key.Key == apiKey {
return p, nil
}
}
}
return nil, nil
}
func (api *Api) ApiKeyAuth(h func(http.ResponseWriter, *http.Request, *db.Project)) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
apiKey := r.Header.Get(collector.ApiKeyHeader)
if apiKey == "" {
klog.Warningln("no api key")
http.Error(w, "no api key", http.StatusBadRequest)
return
}
project, err := api.getProjectByApiKey(apiKey)
if err != nil {
klog.Errorln(err)
http.Error(w, "", http.StatusUnauthorized)
return
}
if project == nil {
klog.Warningln("no project found")
http.Error(w, "no project found", http.StatusNotFound)
return
}
h(w, r, project)
return
}
}
func (api *Api) Login(w http.ResponseWriter, r *http.Request) {
var form forms.LoginForm
if err := forms.ReadAndValidate(r, &form); err != nil {
klog.Warningln("bad request:", err)
http.Error(w, "", http.StatusBadRequest)
return
}
var userId int
switch form.Action {
case "set_admin_password":
admin, err := api.db.DefaultAdminUserIsTheOnlyUser()
if err != nil {
klog.Errorln(err)
http.Error(w, "", http.StatusInternalServerError)
return
}
if admin == nil {
http.Error(w, "The admin password has already been set.", http.StatusUnauthorized)
return
}
err = api.db.ChangeUserPassword(admin.Id, db.AdminUserDefaultPassword, form.Password)
if err != nil {
klog.Errorln(err)
switch {
case errors.Is(err, db.ErrNotFound):
http.Error(w, "User not found.", http.StatusNotFound)
case errors.Is(err, db.ErrInvalid):
http.Error(w, "Invalid old password.", http.StatusBadRequest)
case errors.Is(err, db.ErrConflict):
http.Error(w, "New password can't be the same as the old one.", http.StatusBadRequest)
default:
http.Error(w, "", http.StatusInternalServerError)
}
return
}
userId = admin.Id
default:
id, err := api.db.AuthUser(form.Email, form.Password)
if err != nil {
klog.Errorln(err)
if errors.Is(err, db.ErrNotFound) {
http.Error(w, "Invalid email or password.", http.StatusNotFound)
} else {
http.Error(w, "", http.StatusInternalServerError)
}
return
}
userId = id
}
err := api.SetSessionCookie(w, userId, SessionCookieTTL)
if err != nil {
klog.Errorln(err)
http.Error(w, "", http.StatusInternalServerError)
return
}
}
func (api *Api) Logout(w http.ResponseWriter, r *http.Request) {
http.SetCookie(w, &http.Cookie{
Name: SessionCookieName,
Path: "/",
HttpOnly: true,
})
}
func (api *Api) SetSessionCookie(w http.ResponseWriter, userId int, ttl time.Duration) error {
data, err := json.Marshal(Session{Id: userId})
if err != nil {
return err
}
h := hmac.New(HashFunc.New, []byte(api.authSecret))
h.Write(data)
value := base64.URLEncoding.EncodeToString(data) + "." + base64.URLEncoding.EncodeToString(h.Sum(nil))
http.SetCookie(w, &http.Cookie{
Name: SessionCookieName,
Value: value,
Path: "/",
Expires: time.Now().Add(ttl),
HttpOnly: true,
})
return nil
}
func (api *Api) GetUser(r *http.Request) *db.User {
if user := api.GetUserByApiKey(r); user != nil {
return user
}
if api.authAnonymousRole != "" {
return db.AnonymousUser(api.authAnonymousRole)
}
c, _ := r.Cookie(SessionCookieName)
if c == nil {
return nil
}
parts := strings.Split(c.Value, ".")
if len(parts) != 2 {
klog.Errorln("invalid session")
return nil
}
data, err := base64.URLEncoding.DecodeString(parts[0])
if err != nil {
klog.Errorln(err)
return nil
}
h := hmac.New(HashFunc.New, []byte(api.authSecret))
h.Write(data)
if parts[1] != base64.URLEncoding.EncodeToString(h.Sum(nil)) {
klog.Errorln("invalid session")
return nil
}
var sess Session
err = json.Unmarshal(data, &sess)
if err != nil {
klog.Errorln(err)
return nil
}
user, err := api.db.GetUser(sess.Id)
if err != nil {
klog.Errorln(err)
return nil
}
return user
}
func (api *Api) IsAllowed(u *db.User, actions ...rbac.Action) bool {
roles, err := api.roles.GetRoles()
if err != nil {
klog.Errorln(err)
return false
}
for _, rn := range u.Roles {
for _, r := range roles {
if r.Name != rn {
continue
}
if slices.ContainsFunc(actions, r.Permissions.Allows) {
return true
}
}
}
return false
}