lib/saw/iam.go (98 lines of code) (raw):
// Copyright 2020 Google LLC.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package saw
import (
"fmt"
"regexp"
"time"
"bitbucket.org/creachadair/stringset" /* copybara-comment */
"github.com/GoogleCloudPlatform/healthcare-federated-access-services/lib/globalflags" /* copybara-comment: globalflags */
"github.com/GoogleCloudPlatform/healthcare-federated-access-services/lib/strutil" /* copybara-comment: strutil */
)
const (
// iamVersion use 3 to support iam condition.
iamVersion = 3
)
var (
expiryConditionRE = regexp.MustCompile(`^request\.time < timestamp\("(.*?)"\)$`)
conditionExprFormat = `request.time < timestamp("%s")`
timeNow = time.Now
)
// expiryInCondition finds expiry in condition expression
func expiryInCondition(condition string) time.Time {
if len(condition) == 0 {
return time.Time{}
}
match := expiryConditionRE.FindStringSubmatch(condition)
if len(match) > 1 {
if ts, err := time.Parse(time.RFC3339, match[1]); err == nil {
return ts
}
}
return time.Time{}
}
// toExpiryConditionExpr builds the condition expr with given timestamp
func toExpiryConditionExpr(exp time.Time) string {
timeStr := exp.Format(time.RFC3339)
return fmt.Sprintf(conditionExprFormat, timeStr)
}
// iamBinding nomalize binding struct in gcs and cloudresourcemanager
type iamBinding struct {
role string
members []string
condition *iamCondition
}
// iamBinding nomalize condition expr struct in gcs and cloudresourcemanager
type iamCondition struct {
description string
expression string
location string
title string
}
// addPolicyBinding adds a member to role in bindings.
func addPolicyBinding(bindings []*iamBinding, role, member string, ttl time.Duration) []*iamBinding {
if globalflags.DisableIAMConditionExpiry {
return addPolicyBindingWithConditionExpDisabled(bindings, role, member)
}
return addPolicyBindingWithConditionExpEnabled(bindings, role, member, ttl)
}
// addPolicyBindingWithConditionExpEnabled adds a member to role in bindings with iam condition managed expiry.
func addPolicyBindingWithConditionExpEnabled(bindings []*iamBinding, role, member string, ttl time.Duration) []*iamBinding {
var binding *iamBinding
for _, b := range bindings {
if b.role == role && strutil.SliceOnlyContains(b.members, member) {
binding = b
break
}
}
if binding == nil {
binding = &iamBinding{
role: role,
members: []string{member},
}
bindings = append(bindings, binding)
}
// add the expiry condition to binding.
newExp := timeNow().Add(ttl)
exp := time.Time{}
if binding.condition != nil {
exp = expiryInCondition(binding.condition.expression)
}
// if condition already has expiry after the new request, do not modify.
if exp.Before(newExp) {
binding.condition = &iamCondition{
title: "Expiry",
expression: toExpiryConditionExpr(newExp),
}
}
return bindings
}
// addPolicyBindingWithConditionExpDisabled adds a member to role in bindings.
func addPolicyBindingWithConditionExpDisabled(bindings []*iamBinding, role, member string) []*iamBinding {
var binding *iamBinding
for _, b := range bindings {
if b.role == role {
binding = b
break
}
}
if binding == nil {
binding = &iamBinding{
role: role,
members: []string{member},
}
return append(bindings, binding)
}
if !stringset.Contains(binding.members, member) {
binding.members = append(binding.members, member)
}
return bindings
}