Skip to content

Commit

Permalink
Merge pull request #1 from devwithkrishna/feature/app
Browse files Browse the repository at this point in the history
Feature/app
  • Loading branch information
githubofkrishnadhas authored Aug 5, 2024
2 parents f469472 + dc52643 commit 58ee696
Show file tree
Hide file tree
Showing 7 changed files with 146 additions and 57 deletions.
106 changes: 53 additions & 53 deletions .github/workflows/azure-policy-exemption.yml
Original file line number Diff line number Diff line change
@@ -1,53 +1,53 @@
name: azure-policy-exemption
on:
workflow_dispatch:
inputs:
subscription_name:
description: 'From which subscription we need to provide exemption. the scope'
type: string
required: true
policy_name:
description: 'Policy Name to be given Exception to'
type: string
required: true
expires_after:
description: 'Policy exemption should be automatically revoked after how long'
type: string
required: true
unit:
description: 'Unit of time'
required: true
type: choice
options:
- hour
- day
- month
run-name: policy exemption for ${{ inputs.policy_name }} for ${{ inputs.expires_after }} ${{ inputs.unit }}
jobs:
azure-policy-exemption:
runs-on: ubuntu-latest
env:
AZURE_CLIENT_ID: ${{ secrets.OWNER_SP_APP_ID }}
AZURE_CLIENT_SECRET: ${{ secrets.OWNER_SP_APP_SECRET }}
AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Set up python
uses: actions/setup-python@v5
with:
python-version: '3.11'

- name: Install package mgmt tool
run: |
pip install poetry
poetry install
- name: Execute program
run: |
poetry run python3 policy_exception.py --subscription_name "${{ inputs.subscription_name }}" --policy_name "${{ inputs.policy_name }}" --expires_after ${{ inputs.expires_after }} --unit ${{ inputs.unit }}
- name: Completed
run: echo "Program execution completed"
#name: azure-policy-exemption
#on:
# workflow_dispatch:
# inputs:
# subscription_name:
# description: 'From which subscription we need to provide exemption. the scope'
# type: string
# required: true
# policy_name:
# description: 'Policy Name to be given Exception to'
# type: string
# required: true
# expires_after:
# description: 'Policy exemption should be automatically revoked after how long'
# type: string
# required: true
# unit:
# description: 'Unit of time'
# required: true
# type: choice
# options:
# - hour
# - day
# - month
#run-name: policy exemption for ${{ inputs.policy_name }} for ${{ inputs.expires_after }} ${{ inputs.unit }}
#jobs:
# azure-policy-exemption:
# runs-on: ubuntu-latest
# env:
# AZURE_CLIENT_ID: ${{ secrets.OWNER_SP_APP_ID }}
# AZURE_CLIENT_SECRET: ${{ secrets.OWNER_SP_APP_SECRET }}
# AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
#
# steps:
# - name: Checkout repository
# uses: actions/checkout@v4
#
# - name: Set up python
# uses: actions/setup-python@v5
# with:
# python-version: '3.11'
#
# - name: Install package mgmt tool
# run: |
# pip install poetry
# poetry install
#
# - name: Execute program
# run: |
# poetry run python3 policy_exception.py --subscription_name "${{ inputs.subscription_name }}" --policy_name "${{ inputs.policy_name }}" --expires_after ${{ inputs.expires_after }} --unit ${{ inputs.unit }}
#
# - name: Completed
# run: echo "Program execution completed"
34 changes: 33 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ or operational needs. Automating the process of creating and managing policy exe

Policy exemption automation simplifies the process of managing exceptions to cloud policies.

>[!NOTE]
> Policy Exemptions are applied at Subscription level scope
Here’s a brief overview of how it works:

## Triggering Automation:
Expand Down Expand Up @@ -52,6 +55,35 @@ Use Azure python SDKs to create or update the policy exemption based on the prov

* If value of expires_after is `4` and unit is `month` - the expiration will be after `4 months` of executing the job

# Streamlit UI

* Using streamlit to Create pythin application

![policy-exemption-example.jpeg](policy-exemption-example.jpeg)

* Provide valid Subscription Name

* This returns the Subscription Id corresponding to the Subscription name

* If entered Subscription Name is not found, None value is returned for Subscription Id

* Once a valid subscription Id is returned using it, it will show us all assigned policies at the subscription level

* Select the policy from dropdown which needs exemption

* Provide a expires after value like 1 or 2 or 10 or 4.5 etc and unit value like day or month or hour.

* Click on Apply Exemption to apply exemption.

# Run code locally

* Clone the repository and change direcctory into this

* Install all dependancies using `poetry install`

* Execute `poetry run streamlit run .\streamlit_app.py`


# Azure Python SDKs used
use azure-identity for auth

Expand Down Expand Up @@ -79,5 +111,5 @@ AZURE_CLIENT_SECRET: one of the service principal's client secrets

>[!TIP]
> The policy exemption name length must not exceed '64' characters. I am using the same as policy name for exception.
> You can choose to change it as you see fit
> You can choose to change it as you see fit
7 changes: 6 additions & 1 deletion azure_resource_graph_query.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import azure.mgmt.resourcegraph as arg
import logging
import streamlit as st
from dotenv import load_dotenv
from azure.identity import EnvironmentCredential

Expand Down Expand Up @@ -27,6 +28,10 @@ def run_azure_rg_query(subscription_name: str):
# Run query
arg_result = arg_client.resources(arg_query)

if not arg_result.data:
# Handle the case where no subscription ID is found
st.error(f"No subscription found with the name '{subscription_name}'. Please verify the name and try again.")
return None
subscription_id = arg_result.data[0]['subscriptionId']
print(f"Subscription ID is : {subscription_id}")
return subscription_id
Expand All @@ -38,7 +43,7 @@ def main():
"""
load_dotenv()
logging.info("ARG query being prepared......")
run_azure_rg_query(subscription_name="TECH-ARCHITECTS-NONPROD")
run_azure_rg_query(subscription_name="TECH-CLOUD-PROD")
logging.info("ARG query Completed......")


Expand Down
Binary file added policy-exemption-example.jpeg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
13 changes: 11 additions & 2 deletions policy_exception.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import os
import argparse
import streamlit as st
from dotenv import load_dotenv
from azure.identity import EnvironmentCredential
from azure.mgmt.resource.policy.v2022_06_01 import PolicyClient
Expand All @@ -17,6 +18,12 @@ class PolicyAssignmentList(BaseModel):
policy_definition_id : str
scope : str

def get_policies(subscription_id: str):
# Retrieve all policies in the subscription
credential = EnvironmentCredential()
client = PolicyClient(credential=credential, subscription_id=subscription_id)
policy_assignment_list = client.policy_assignments.list()
return [policy.display_name for policy in policy_assignment_list]

def extract_policy_data(subscription_id: str) -> PolicyAssignmentList:
"""
Expand Down Expand Up @@ -57,6 +64,7 @@ def verify_policy_is_available(subscription_id:str, policy_name: str):
for policy in policy_assignment_list:
if policy_name == policy.display_name:
print(f"Found policy assignment for '{policy_name}' in scope {subscription_id}")
st.write(f"Found policy assignment for '{policy_name}' in scope {subscription_id}")
# convert policy obj to dict
policy_to_be_exempted = policy.__dict__
break # Exit the loop once the policy is found
Expand All @@ -80,13 +88,13 @@ def create_exemption_for_policy(subscription_id: str, policy_name:str, expires_a
policy_to_be_exempted = verify_policy_is_available(subscription_id=subscription_id, policy_name=policy_name)

scope = f"/subscriptions/{subscription_id}"

st.write(f"Scope of exemption is : /subscriptions/{subscription_id}")
policy_exemption_name = f'exemption for {policy_name}' # <policy name> - <yyyy-<month short form>-<day> HH:MM")>
print(f'Policy Exemption name will be "{policy_exemption_name}"')
st.write(f'Policy Exemption name will be "{policy_exemption_name}"')
expiry_date = calculate_expiry(expires_after=expires_after, unit=unit)
policy_exemption_description = f'exemption for {policy_name}'


try:

parameters = PolicyExemption(
Expand All @@ -99,6 +107,7 @@ def create_exemption_for_policy(subscription_id: str, policy_name:str, expires_a

exemption = client.policy_exemptions.create_or_update(scope=scope,policy_exemption_name=policy_exemption_name, parameters=parameters)
print("Policy exemption created or updated successfully.")
st.write(f"Policy exemption created or updated successfully. Policy Exemption will expire at {expiry_date}")
print(f'Policy Exemption will expire at {expiry_date}')

except HttpResponseError as err:
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ pydantic = "^2.8.2"
python-dateutil = "^2.9.0.post0"
pytz = "^2024.1"
azure-core = "^1.30.2"
streamlit = "1.36.0"


[build-system]
Expand Down
42 changes: 42 additions & 0 deletions streamlit_app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import streamlit as st
from dotenv import load_dotenv
from policy_exception import create_exemption_for_policy, get_policies
from azure_resource_graph_query import run_azure_rg_query


def main():
"""run streamlit app"""
load_dotenv()
st.header("Azure Policy Exemption Tool", divider='rainbow')
subscription_name = st.text_input("Enter Subscription Name")
if subscription_name:
st.session_state.subscription_name = subscription_name
subscription_id = run_azure_rg_query(subscription_name=subscription_name)
st.session_state.subscription_id = subscription_id
st.success(f"Subscription ID of {subscription_name}: {subscription_id}")
else:
st.error(f"Subscription {subscription_name} not found")
if subscription_id:
# if 'subscription_id' in st.session_state:
policies = get_policies(subscription_id=subscription_id)
selected_policy = st.selectbox("Select a Policy", policies)
if selected_policy:
st.write(f"You selected: {selected_policy}")
st.session_state.selected_policy = selected_policy

expires_after = st.text_input("Policy Will Expires After")
unit = st.selectbox("Unit", ["hour", "day", "month"])

# Print the exemption period
st.write(f"Policy exemption will expire after {expires_after} {unit}")

# Run streamlit app by clicking submit
if st.button("Apply Exemption"):
# call policy exemption creation function
create_exemption_for_policy(subscription_id=subscription_id, policy_name=selected_policy,
expires_after=expires_after, unit=unit)



if __name__ == "__main__":
main()

0 comments on commit 58ee696

Please sign in to comment.