Skip to content

Commit 58ee696

Browse files
Merge pull request #1 from devwithkrishna/feature/app
Feature/app
2 parents f469472 + dc52643 commit 58ee696

File tree

7 files changed

+146
-57
lines changed

7 files changed

+146
-57
lines changed
Lines changed: 53 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,53 @@
1-
name: azure-policy-exemption
2-
on:
3-
workflow_dispatch:
4-
inputs:
5-
subscription_name:
6-
description: 'From which subscription we need to provide exemption. the scope'
7-
type: string
8-
required: true
9-
policy_name:
10-
description: 'Policy Name to be given Exception to'
11-
type: string
12-
required: true
13-
expires_after:
14-
description: 'Policy exemption should be automatically revoked after how long'
15-
type: string
16-
required: true
17-
unit:
18-
description: 'Unit of time'
19-
required: true
20-
type: choice
21-
options:
22-
- hour
23-
- day
24-
- month
25-
run-name: policy exemption for ${{ inputs.policy_name }} for ${{ inputs.expires_after }} ${{ inputs.unit }}
26-
jobs:
27-
azure-policy-exemption:
28-
runs-on: ubuntu-latest
29-
env:
30-
AZURE_CLIENT_ID: ${{ secrets.OWNER_SP_APP_ID }}
31-
AZURE_CLIENT_SECRET: ${{ secrets.OWNER_SP_APP_SECRET }}
32-
AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
33-
34-
steps:
35-
- name: Checkout repository
36-
uses: actions/checkout@v4
37-
38-
- name: Set up python
39-
uses: actions/setup-python@v5
40-
with:
41-
python-version: '3.11'
42-
43-
- name: Install package mgmt tool
44-
run: |
45-
pip install poetry
46-
poetry install
47-
48-
- name: Execute program
49-
run: |
50-
poetry run python3 policy_exception.py --subscription_name "${{ inputs.subscription_name }}" --policy_name "${{ inputs.policy_name }}" --expires_after ${{ inputs.expires_after }} --unit ${{ inputs.unit }}
51-
52-
- name: Completed
53-
run: echo "Program execution completed"
1+
#name: azure-policy-exemption
2+
#on:
3+
# workflow_dispatch:
4+
# inputs:
5+
# subscription_name:
6+
# description: 'From which subscription we need to provide exemption. the scope'
7+
# type: string
8+
# required: true
9+
# policy_name:
10+
# description: 'Policy Name to be given Exception to'
11+
# type: string
12+
# required: true
13+
# expires_after:
14+
# description: 'Policy exemption should be automatically revoked after how long'
15+
# type: string
16+
# required: true
17+
# unit:
18+
# description: 'Unit of time'
19+
# required: true
20+
# type: choice
21+
# options:
22+
# - hour
23+
# - day
24+
# - month
25+
#run-name: policy exemption for ${{ inputs.policy_name }} for ${{ inputs.expires_after }} ${{ inputs.unit }}
26+
#jobs:
27+
# azure-policy-exemption:
28+
# runs-on: ubuntu-latest
29+
# env:
30+
# AZURE_CLIENT_ID: ${{ secrets.OWNER_SP_APP_ID }}
31+
# AZURE_CLIENT_SECRET: ${{ secrets.OWNER_SP_APP_SECRET }}
32+
# AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
33+
#
34+
# steps:
35+
# - name: Checkout repository
36+
# uses: actions/checkout@v4
37+
#
38+
# - name: Set up python
39+
# uses: actions/setup-python@v5
40+
# with:
41+
# python-version: '3.11'
42+
#
43+
# - name: Install package mgmt tool
44+
# run: |
45+
# pip install poetry
46+
# poetry install
47+
#
48+
# - name: Execute program
49+
# run: |
50+
# poetry run python3 policy_exception.py --subscription_name "${{ inputs.subscription_name }}" --policy_name "${{ inputs.policy_name }}" --expires_after ${{ inputs.expires_after }} --unit ${{ inputs.unit }}
51+
#
52+
# - name: Completed
53+
# run: echo "Program execution completed"

README.md

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ or operational needs. Automating the process of creating and managing policy exe
1717

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

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

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

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

58+
# Streamlit UI
59+
60+
* Using streamlit to Create pythin application
61+
62+
![policy-exemption-example.jpeg](policy-exemption-example.jpeg)
63+
64+
* Provide valid Subscription Name
65+
66+
* This returns the Subscription Id corresponding to the Subscription name
67+
68+
* If entered Subscription Name is not found, None value is returned for Subscription Id
69+
70+
* Once a valid subscription Id is returned using it, it will show us all assigned policies at the subscription level
71+
72+
* Select the policy from dropdown which needs exemption
73+
74+
* Provide a expires after value like 1 or 2 or 10 or 4.5 etc and unit value like day or month or hour.
75+
76+
* Click on Apply Exemption to apply exemption.
77+
78+
# Run code locally
79+
80+
* Clone the repository and change direcctory into this
81+
82+
* Install all dependancies using `poetry install`
83+
84+
* Execute `poetry run streamlit run .\streamlit_app.py`
85+
86+
5587
# Azure Python SDKs used
5688
use azure-identity for auth
5789

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

80112
>[!TIP]
81113
> The policy exemption name length must not exceed '64' characters. I am using the same as policy name for exception.
82-
> You can choose to change it as you see fit
114+
> You can choose to change it as you see fit
83115

azure_resource_graph_query.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import azure.mgmt.resourcegraph as arg
22
import logging
3+
import streamlit as st
34
from dotenv import load_dotenv
45
from azure.identity import EnvironmentCredential
56

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

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

4449

policy-exemption-example.jpeg

97.4 KB
Loading

policy_exception.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import os
22
import argparse
3+
import streamlit as st
34
from dotenv import load_dotenv
45
from azure.identity import EnvironmentCredential
56
from azure.mgmt.resource.policy.v2022_06_01 import PolicyClient
@@ -17,6 +18,12 @@ class PolicyAssignmentList(BaseModel):
1718
policy_definition_id : str
1819
scope : str
1920

21+
def get_policies(subscription_id: str):
22+
# Retrieve all policies in the subscription
23+
credential = EnvironmentCredential()
24+
client = PolicyClient(credential=credential, subscription_id=subscription_id)
25+
policy_assignment_list = client.policy_assignments.list()
26+
return [policy.display_name for policy in policy_assignment_list]
2027

2128
def extract_policy_data(subscription_id: str) -> PolicyAssignmentList:
2229
"""
@@ -57,6 +64,7 @@ def verify_policy_is_available(subscription_id:str, policy_name: str):
5764
for policy in policy_assignment_list:
5865
if policy_name == policy.display_name:
5966
print(f"Found policy assignment for '{policy_name}' in scope {subscription_id}")
67+
st.write(f"Found policy assignment for '{policy_name}' in scope {subscription_id}")
6068
# convert policy obj to dict
6169
policy_to_be_exempted = policy.__dict__
6270
break # Exit the loop once the policy is found
@@ -80,13 +88,13 @@ def create_exemption_for_policy(subscription_id: str, policy_name:str, expires_a
8088
policy_to_be_exempted = verify_policy_is_available(subscription_id=subscription_id, policy_name=policy_name)
8189

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

89-
9098
try:
9199

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

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

104113
except HttpResponseError as err:

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ pydantic = "^2.8.2"
1616
python-dateutil = "^2.9.0.post0"
1717
pytz = "^2024.1"
1818
azure-core = "^1.30.2"
19+
streamlit = "1.36.0"
1920

2021

2122
[build-system]

streamlit_app.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import streamlit as st
2+
from dotenv import load_dotenv
3+
from policy_exception import create_exemption_for_policy, get_policies
4+
from azure_resource_graph_query import run_azure_rg_query
5+
6+
7+
def main():
8+
"""run streamlit app"""
9+
load_dotenv()
10+
st.header("Azure Policy Exemption Tool", divider='rainbow')
11+
subscription_name = st.text_input("Enter Subscription Name")
12+
if subscription_name:
13+
st.session_state.subscription_name = subscription_name
14+
subscription_id = run_azure_rg_query(subscription_name=subscription_name)
15+
st.session_state.subscription_id = subscription_id
16+
st.success(f"Subscription ID of {subscription_name}: {subscription_id}")
17+
else:
18+
st.error(f"Subscription {subscription_name} not found")
19+
if subscription_id:
20+
# if 'subscription_id' in st.session_state:
21+
policies = get_policies(subscription_id=subscription_id)
22+
selected_policy = st.selectbox("Select a Policy", policies)
23+
if selected_policy:
24+
st.write(f"You selected: {selected_policy}")
25+
st.session_state.selected_policy = selected_policy
26+
27+
expires_after = st.text_input("Policy Will Expires After")
28+
unit = st.selectbox("Unit", ["hour", "day", "month"])
29+
30+
# Print the exemption period
31+
st.write(f"Policy exemption will expire after {expires_after} {unit}")
32+
33+
# Run streamlit app by clicking submit
34+
if st.button("Apply Exemption"):
35+
# call policy exemption creation function
36+
create_exemption_for_policy(subscription_id=subscription_id, policy_name=selected_policy,
37+
expires_after=expires_after, unit=unit)
38+
39+
40+
41+
if __name__ == "__main__":
42+
main()

0 commit comments

Comments
 (0)