Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

audit-followup: remove stale iam bindings for e2e projects #1722

Merged
merged 2 commits into from
Feb 25, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion infra/gcp/ensure-organization.sh
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ fi
## setup custom role for prow troubleshooting
color 6 "Ensuring custom org role prow.viewer role exists"
(
ensure_custom_org_role_from_file "prow.viewer" "${SCRIPT_DIR}/roles/prow.viewer.yaml"
ensure_custom_iam_role_from_file "org" "prow.viewer" "${SCRIPT_DIR}/roles/prow.viewer.yaml"
) 2>&1 | indent

color 6 "Ensuring org-level IAM bindings exist"
Expand Down
98 changes: 1 addition & 97 deletions infra/gcp/lib.sh
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
# This is a library of functions used to create GCP stuff.

. "$(dirname "${BASH_SOURCE[0]}")/lib_util.sh"
. "$(dirname "${BASH_SOURCE[0]}")/lib_iam.sh"
. "$(dirname "${BASH_SOURCE[0]}")/lib_gcr.sh"
. "$(dirname "${BASH_SOURCE[0]}")/lib_gcs.sh"

Expand Down Expand Up @@ -576,100 +577,3 @@ function ensure_regional_address() {
--region="${region}"
fi
}

# Ensure that custom IAM role exists, creating one if needed
# Arguments:
# $1: The GCP project
# $2: The role name (e.g. "ServiceAccountLister")
# $3: The role title (e.g. "Service Account Lister")
# $4: The role description (e.g. "Can list ServiceAccounts.")
# $5+: The role permissions (e.g. "iam.serviceAccounts.list")
# Example usage:
# ensure_custom_iam_role \
# kubernetes-public \
# ServiceAccountLister \
# "Service Account Lister" \
# "Can list ServiceAccounts." \
# iam.serviceAccounts.list
function ensure_custom_iam_role() {
if [ $# -lt 5 ] || [ -z "${1}" ] || [ -z "${2}" ] || [ -z "${3}" ] \
|| [ -z "${4}" ] || [ -z "${5}" ]
then
echo -n "ensure_custom_iam_role(gcp_project, name, title," >&2
echo " description, permission...) requires at least 5 arguments" >&2
return 1
fi

local gcp_project="${1}"; shift
local name="${1}"; shift
local title="${1}"; shift
local description="${1}"; shift
local permissions; permissions=$(join_by , "$@")

if ! gcloud --project "${gcp_project}" iam roles describe "${name}" \
>/dev/null 2>&1
then
gcloud --project "${gcp_project}" --quiet \
iam roles create "${name}" \
--title "${title}" \
--description "${description}" \
--stage GA \
--permissions "${permissions}"
fi
}

# Ensure that custom IAM role exists and is in sync with definition in file
# Arguments:
# $1: The role name (e.g. "prow.viewer")
# $2: The file (e.g. "/path/to/file.yaml")
function ensure_custom_org_role_from_file() {
if [ ! $# -eq 2 -o -z "$1" -o -z "$2" ]; then
echo "ensure_custom_org_role_from_file(name, file) requires 2 arguments" >&2
return 1
fi

local org="${GCP_ORG}"
local name="${1}"
local file="${2}"

if ! gcloud iam roles describe "${name}" --organization "${org}" \
>/dev/null 2>&1
then
# be noisy when creating a role
gcloud iam roles create "${name}" --organization "${org}" --file "${file}"
else
# be quiet when updating, only output name of role
gcloud iam roles update "${name}" --organization "${org}" --file "${file}" | grep ^name:
fi
}

function custom_org_role_name() {
if [ ! $# -eq 1 -o -z "$1" ]; then
echo "custom_org_role_name(name) requires 1 arguments" >&2
return 1
fi

local name="${1}"

echo "organizations/${GCP_ORG}/roles/${name}"
}

# Ensure that IAM binding exists at org level
# Arguments:
# $1: The role name (e.g. "prow.viewer")
# $2: The file (e.g. "/path/to/file.yaml")
function ensure_org_role_binding() {
if [ ! $# -eq 2 -o -z "$1" -o -z "$2" ]; then
echo "ensure_org_role_binding(principal, role) requires 2 arguments" >&2
return 1
fi

local org="${GCP_ORG}"
local principal="${1}"
local role="${2}"

gcloud \
organizations add-iam-policy-binding "${GCP_ORG}" \
--member "${principal}" \
--role "${role}"
}
200 changes: 200 additions & 0 deletions infra/gcp/lib_iam.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
#!/usr/bin/env bash
#
# Copyright 2021 The Kubernetes Authors.
#
# 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.

# IAM utility functions
#
# This is intended to be very general-purpose and "low-level". Higher-level
# policy does not belong here.
#
# This MUST NOT be used directly. Source it via lib.sh instead.

# Ensure that custom IAM role exists, creating one if needed
# Arguments:
# $1: The GCP project
# $2: The role name (e.g. "ServiceAccountLister")
# $3: The role title (e.g. "Service Account Lister")
# $4: The role description (e.g. "Can list ServiceAccounts.")
# $5+: The role permissions (e.g. "iam.serviceAccounts.list")
# Example usage:
# ensure_custom_iam_role \
# kubernetes-public \
# ServiceAccountLister \
# "Service Account Lister" \
# "Can list ServiceAccounts." \
# iam.serviceAccounts.list
function ensure_custom_iam_role() {
if [ $# -lt 5 ] || [ -z "${1}" ] || [ -z "${2}" ] || [ -z "${3}" ] \
|| [ -z "${4}" ] || [ -z "${5}" ]
then
echo -n "ensure_custom_iam_role(gcp_project, name, title," >&2
echo " description, permission...) requires at least 5 arguments" >&2
return 1
fi

local gcp_project="${1}"; shift
local name="${1}"; shift
local title="${1}"; shift
local description="${1}"; shift
local permissions; permissions=$(join_by , "$@")

if ! gcloud --project "${gcp_project}" iam roles describe "${name}" \
>/dev/null 2>&1
then
gcloud --project "${gcp_project}" --quiet \
iam roles create "${name}" \
--title "${title}" \
--description "${description}" \
--stage GA \
--permissions "${permissions}"
fi
}

# Ensure that custom IAM role exists and is in sync with definition in file
# Arguments:
# $1: The scope of the role (e.g. "org", "project:foobar")
# $2: The role name (e.g. "prow.viewer")
# $3: The file (e.g. "/path/to/file.yaml")
function ensure_custom_iam_role_from_file() {
if [ ! $# -eq 3 -o -z "$1" -o -z "$2" -o -z "$3" ]; then
echo "ensure_custom_iam_role_from_file(scope, name, file) requires 3 arguments" >&2
return 1
fi

local scope="${1}"
local name="${2}"
local file="${3}"

scope_flag=""
if [[ "${scope}" == "org" ]]; then
scope_flag="--organization ${GCP_ORG}"
elif [[ "${scope}" =~ "^project:" ]]; then
scope_flag="--project $(echo ${scope} | cut -d: -f2-)"
else
echo "ensure_custom_iam_role_from_file(scope, name, file) scope must be one of 'org' or 'project:project-id'" >&2
return 1
fi

if ! gcloud iam roles describe ${scope_flag} "${name}" \
>/dev/null 2>&1
then
# be noisy when creating a role
gcloud iam roles create ${scope_flag} "${name}" --file "${file}"
else
# be quiet when updating, only output name of role
gcloud iam roles update ${scope_flag} "${name}" --file "${file}" | grep ^name:
fi
}

# Return the full name of a custom IAM role defined at the org level
# Arguments:
# $1: The role name (e.g. "prow.viewer")
function custom_org_role_name() {
if [ ! $# -eq 1 -o -z "$1" ]; then
echo "custom_org_role_name(name) requires 1 arguments" >&2
return 1
fi

local name="${1}"

echo "organizations/${GCP_ORG}/roles/${name}"
}

# Ensure that IAM binding exists at org level
# Arguments:
# $1: The role name (e.g. "prow.viewer")
# $2: The file (e.g. "/path/to/file.yaml")
function ensure_org_role_binding() {
if [ ! $# -eq 2 -o -z "$1" -o -z "$2" ]; then
echo "ensure_org_role_binding(principal, role) requires 2 arguments" >&2
return 1
fi

local org="${GCP_ORG}"
local principal="${1}"
local role="${2}"

gcloud \
organizations add-iam-policy-binding "${GCP_ORG}" \
--member "${principal}" \
--role "${role}"
}

# Ensure that IAM binding exists at project level
# Arguments:
# $1: The project id (e.g. "k8s-infra-foo")
# $2: The principal (e.g. "group:[email protected]")
# $3: The role name (e.g. "roles/storage.objectAdmin")
function ensure_project_role_binding() {
if [ ! $# -eq 3 -o -z "$1" -o -z "$2" -o -z "$3" ]; then
echo "ensure_project_role_binding(project, principal, role) requires 3 arguments" >&2
return 1
fi

local project="${1}"
local principal="${2}"
local role="${3}"

gcloud \
projects add-iam-policy-binding "${project}" \
--member "${principal}" \
--role "${role}"
}

# Ensure that IAM binding has been removed at project level
# Arguments:
# $1: The project id (e.g. "k8s-infra-foo")
# $2: The principal (e.g. "group:[email protected]")
# $3: The role name (e.g. "roles/foo.bar")
function ensure_removed_project_role_binding() {
if [ ! $# -eq 3 -o -z "$1" -o -z "$2" -o -z "$3" ]; then
echo "ensure_removed_project_role_binding(project, principal, role) requires 3 arguments" >&2
return 1
fi
local project="${1}"
local principal="${2}"
local role="${3}"

_ensure_removed_resource_role_binding "projects" "${project}" "${principal}" "${role}"
}

# Ensure that IAM binding has been removed at resource level
# Arguments:
# $1: The resource type (e.g. "projects", "organizations", "secrets" )
# $2: The id of the resource (e.g. "k8s-infra-foo", "12345")
# $3: The principal (e.g. "group:[email protected]")
# $4: The role name (e.g. "roles/foo.bar")
function _ensure_removed_resource_role_binding() {
if [ ! $# -eq 4 -o -z "$1" -o -z "$2" -o -z "$3" -o -z "$4" ]; then
echo "ensure_removed_project_role_binding(resource, id, principal, role) requires 4 arguments" >&2
return 1
fi

local resource="${1}"
local id="${2}"
local principal="${3}"
local role="${4}"

# gcloud remove-iam-policy-binding errors if binding doesn't exist, so confirm it does
if gcloud "${resource}" get-iam-policy "${id}" \
--flatten="bindings[].members" \
--format='value(bindings.role)' \
--filter="bindings.members='${principal}' AND bindings.role='${role}'" | grep -q "${role}"; then
gcloud \
"${resource}" remove-iam-policy-binding "${id}" \
--member "${principal}" \
--role "${role}"
fi
}
14 changes: 12 additions & 2 deletions infra/gcp/prow/ensure-e2e-projects.sh
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ set -o nounset
set -o pipefail

SCRIPT_DIR=$(dirname "${BASH_SOURCE[0]}")
. "${SCRIPT_DIR}/lib.sh"
. "${SCRIPT_DIR}/../lib.sh"
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixes regression introduced in #1636


function usage() {
echo "usage: $0 [repo...]" > /dev/stderr
Expand Down Expand Up @@ -111,6 +111,16 @@ for prj; do
(
ensure_project "${prj}"

color 6 "Ensure stale role bindings have been removed from e2e project: ${prj}"
(
# TODO(https://github.com/kubernetes/k8s.io/issues/1661): remove once verified as consistent
color 6 "group:[email protected] should not have roles/viewer (ref: https://github.com/kubernetes/k8s.io/issues/1661)"
ensure_removed_project_role_binding "${prj}" "group:[email protected]" "roles/viewer"
# TODO(https://github.com/kubernetes/k8s.io/issues/299): remove once smarter logic folded into ensure_project
color 6 "user:* should not have roles/owner for projects (ref: https://github.com/kubernetes/k8s.io/issues/299)"
ensure_removed_project_role_binding "${prj}" "[email protected]" "roles/owner"
) 2>&1 | indent

color 6 "Enabling APIs necessary for kubernetes e2e jobs to use e2e project: ${prj}"
enable_api "${prj}" compute.googleapis.com
enable_api "${prj}" logging.googleapis.com
Expand Down Expand Up @@ -143,7 +153,7 @@ for prj; do
--role roles/owner

# NB: prow.viewer role is defined in ensure-organization.sh, that needs to have been run first
color 6 "Empower [email protected] to view e2e project: ${prj}"
color 6 "Empower [email protected] to view specific resources in e2e project: ${prj}"
gcloud \
projects add-iam-policy-binding "${prj}" \
--member "group:[email protected]" \
Expand Down