go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/logdog/appengine/coordinator/auth.go (about) 1 // Copyright 2015 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package coordinator 16 17 import ( 18 "context" 19 20 "google.golang.org/grpc/codes" 21 "google.golang.org/grpc/status" 22 23 "go.chromium.org/luci/auth/identity" 24 "go.chromium.org/luci/common/logging" 25 "go.chromium.org/luci/logdog/common/types" 26 "go.chromium.org/luci/logdog/server/config" 27 "go.chromium.org/luci/server/auth" 28 "go.chromium.org/luci/server/auth/realms" 29 ) 30 31 var ( 32 // PermLogsCreate is a permission required for RegisterPrefix RPC. 33 PermLogsCreate = realms.RegisterPermission("logdog.logs.create") 34 // PermLogsGet is a permission required for reading individual streams. 35 PermLogsGet = realms.RegisterPermission("logdog.logs.get") 36 // PermLogsList is a permission required for listing streams in a prefix. 37 PermLogsList = realms.RegisterPermission("logdog.logs.list") 38 ) 39 40 // PermissionDeniedErr is a generic "doesn't exist or don't have access" error. 41 // 42 // If the request is anonymous, it is an Unauthenticated error instead. 43 func PermissionDeniedErr(ctx context.Context) error { 44 if id := auth.CurrentIdentity(ctx); id.Kind() == identity.Anonymous { 45 return status.Error(codes.Unauthenticated, "Authentication required.") 46 } 47 return status.Errorf(codes.PermissionDenied, 48 "The resource doesn't exist or you do not have permission to access it.") 49 } 50 51 // CheckPermission checks the caller has the requested permission. 52 // 53 // Logs the outcome inside (`prefix` is used only in this logging). Returns 54 // gRPC errors that can be returned directly to the caller. 55 func CheckPermission(ctx context.Context, perm realms.Permission, prefix types.StreamName, realm string) error { 56 // Log all the details to help debug permission issues. 57 ctx = logging.SetFields(ctx, logging.Fields{ 58 "identity": auth.CurrentIdentity(ctx), 59 "perm": perm, 60 "prefix": prefix, 61 "realm": realm, 62 }) 63 64 // Check no cross-project mix up is happening as a precaution. 65 project := Project(ctx) 66 if projInRealm, _ := realms.Split(realm); projInRealm != project { 67 logging.Errorf(ctx, "Unexpectedly checking realm %q in a context of project %q", realm, project) 68 return status.Error(codes.Internal, "internal server error") 69 } 70 71 // Do the realms ACL check. 72 switch granted, err := auth.HasPermission(ctx, perm, realm, nil); { 73 case err != nil: 74 logging.WithError(err).Errorf(ctx, "failed to check realms ACL") 75 return status.Error(codes.Internal, "internal server error") 76 case granted: 77 logging.Debugf(ctx, "Permission granted") 78 return nil 79 default: 80 logging.Warningf(ctx, "Permission denied") 81 return PermissionDeniedErr(ctx) 82 } 83 } 84 85 // CheckAdminUser tests whether the current user belongs to the administrative 86 // users group. 87 // 88 // Logs the outcome inside. The error is non-nil only if the check itself fails. 89 func CheckAdminUser(ctx context.Context) (bool, error) { 90 cfg, err := config.Config(ctx) 91 if err != nil { 92 logging.WithError(err).Errorf(ctx, "Failed to load service config") 93 return false, err 94 } 95 return checkMember(ctx, "ADMIN", cfg.Coordinator.AdminAuthGroup) 96 } 97 98 // CheckServiceUser tests whether the current user belongs to the backend 99 // services users group. 100 // 101 // Logs the outcome inside. The error is non-nil only if the check itself fails. 102 func CheckServiceUser(ctx context.Context) (bool, error) { 103 cfg, err := config.Config(ctx) 104 if err != nil { 105 logging.WithError(err).Errorf(ctx, "Failed to load service config") 106 return false, err 107 } 108 return checkMember(ctx, "SERVICE", cfg.Coordinator.ServiceAuthGroup) 109 } 110 111 func checkMember(ctx context.Context, action string, groups ...string) (bool, error) { 112 switch yes, err := auth.IsMember(ctx, groups...); { 113 case err != nil: 114 logging.Fields{ 115 "identity": auth.CurrentIdentity(ctx), 116 "groups": groups, 117 logging.ErrorKey: err, 118 }.Errorf(ctx, "Membership check failed") 119 return false, err 120 case yes: 121 logging.Fields{ 122 "identity": auth.CurrentIdentity(ctx), 123 "groups": groups, 124 }.Debugf(ctx, "User %s access granted.", action) 125 return true, nil 126 default: 127 logging.Fields{ 128 "identity": auth.CurrentIdentity(ctx), 129 "groups": groups, 130 }.Warningf(ctx, "User %s access denied.", action) 131 return false, nil 132 } 133 }