commit c0ad223ac579128c76a00af049fd7145938139cf Author: phermeys Date: Fri Nov 7 09:40:06 2025 -0800 init commit diff --git a/defaults/main.yaml b/defaults/main.yaml new file mode 100644 index 0000000..211ccbe --- /dev/null +++ b/defaults/main.yaml @@ -0,0 +1,21 @@ +--- + +service_user: "cosmos" + +gpo_yaml_share: "/media/gpo_yaml" +gpo_yaml_path: "{{ gpo_yaml_share }}/gpo.yaml" + +project_folder: "/opt/cosmos/gpo_site" + +api_service_name: "gpo_api" +api_service_folder: "{{ project_folder }}/api" +api_service_port: "5000" +api_service_bind_ip: "0.0.0.0" + +dashboard_web_root: "{{ project_folder }}/dashboard" +container_name: "GPO-Dashboard" +container_service_name: "gpo_dash" +container_http_port: "80" +extra_volumes: "" + +... \ No newline at end of file diff --git a/files/dashboard/index.php b/files/dashboard/index.php new file mode 100644 index 0000000..c8351a2 --- /dev/null +++ b/files/dashboard/index.php @@ -0,0 +1,71 @@ + + + + + + + + GPO Query + + + +

Query GPOs by Group

+ +
+ + + +
+ +
+ + + +
+ + +Error: {$data['error']}

"; + } else { + echo ""; + foreach ($data as $item) { + echo ""; + } + echo "
AD GroupGPOLocal Group
{$item['ad_group']}{$item['gpo']}{$item['local_group']}
"; + } + + // Close cURL session + curl_close($ch); +} else { + echo "

No group specified.

"; +} +?> + + + \ No newline at end of file diff --git a/files/dashboard/styles.css b/files/dashboard/styles.css new file mode 100644 index 0000000..e7c72ba --- /dev/null +++ b/files/dashboard/styles.css @@ -0,0 +1,92 @@ +/* styles.css */ + +body { + font-family: Arial, sans-serif; + margin: 0; + padding: 0; + background-color: #2c3e50; /* Dark background color */ + color: #bdc3c7; /* Dimmer text color */ +} + +.hidden-info { + display: none; +} + +.title-button { + background-color: #34495e; + border: none; + color: white; + padding: 15px 32px; + text-align: center; + text-decoration: none; + display: inline-block; + font-size: 16px; + margin: 4px 2px; + cursor: pointer; +} +.container { + max-width: 950px; + margin: 0 auto; + padding: 20px; + background-color: #34495e; /* Darker background for container */ + border-radius: 8px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3); /* Slightly darker shadow */ + margin-top: 20px; +} + +h1, h2, h3, h4 { + color: #bdc3c7; /* Dimmer text color */ +} + +ul { + list-style-type: none; + padding: 0; +} + +li { + margin-bottom: 10px; + color: #bdc3c7; /* Dimmer text color */ +} + +.group-columns { + display: flex; +} + +.group-rows { + display: flex; + flex-wrap: wrap; + justify-content: flex-start; /* Left justification */ + margin-top: 10px; +} + +.group-column { + flex: 0 0 calc(33% - 10px); /* Adjust width of each column */ +} + +.column { + flex: 1; + padding: 0 10px; /* Adjust spacing between columns */ +} + +.subcolumn { + margin-left: 10px; +} + +.grid { + display: flex; + flex-wrap: wrap; + justify-content: space-between; + margin-top: 5px; +} + +.meter { + width: calc(90% - 5px); + max-width: calc(45% - 5px); + margin-bottom: 5px; + border: 1px solid #7f8c8d; /* Light border color */ + border-radius: 5px; + padding: 5px; + text-align: center; + background-color: #2c3e50; /* Dark background for meter */ +} + diff --git a/files/restricted-group-check.ps1 b/files/restricted-group-check.ps1 new file mode 100644 index 0000000..20ab9c9 --- /dev/null +++ b/files/restricted-group-check.ps1 @@ -0,0 +1,112 @@ + # ------------------------------------------------------------------ +# 0. Load modules (they are usually loaded by default, but be safe) +# ------------------------------------------------------------------ + +Import-Module GroupPolicy -ErrorAction Stop +Import-Module ActiveDirectory -ErrorAction Stop +$TargetOU = "OU=NA,OU=Manufacturing,OU=Tesla Systems,DC=teslamotors,DC=com" +$OutputFile = "C:\Users\matanderson\gpo.yaml" +# Add-Content -Path $OutputFile -Value $line +if (Test-Path $OutputFile) { + Remove-Item -Path $OutputFile -Force +} + +# ------------------------------------------------------------------ +# 1. Grab every OU under $TargetOU (including $TargetOU itself) +# ------------------------------------------------------------------ + +Write-Output "Enumerating OUs under '$TargetOU' ..." +Add-Content -Path $OutputFile -Value "---" +Add-Content -Path $OutputFile -Value "- root_ou: $TargetOU" +$ouObjects = Get-ADObject ` + -Filter 'ObjectClass -eq "organizationalUnit"' ` + -SearchBase $TargetOU ` + -SearchScope Subtree ` + -Properties gPLink + +# Also grab the target OU itself (in case it is linked directly) +$targetOUObj = Get-ADObject ` + -Identity $TargetOU ` + -Properties gPLink +if ($targetOUObj) { + $ouObjects += $targetOUObj +} + +# ------------------------------------------------------------------ +# 2. Build a hashtable: GPO_GUID => list of OU DNs it is linked to +# This is entirely opaque voodoo, i have no idea what's going on +# ------------------------------------------------------------------ + +$gpoLinks = @{} + +foreach ($ou in $ouObjects) { + if ($ou.gPLink) { + foreach ($link in ($ou.gPLink -split ';')) { + $linkTrim = $link.Trim('[', ']') + if ($linkTrim -match '\{(?[0-9a-fA-F-]+)\}') { + $guid = $Matches.guid + if (-not $gpoLinks.ContainsKey($guid)) { + $gpoLinks[$guid] = @() + } + $gpoLinks[$guid] += $ou.DistinguishedName + } + } + } +} + +if (-not $gpoLinks.Count) { + Write-Host "No GPOs linked under the searched OU tree." -ForegroundColor Yellow + exit +} + +Write-Host "Found $($gpoLinks.Count) distinct GPO(s) linked in the OU tree." +Add-Content -Path $OutputFile -Value " gpo_links: $($gpoLinks.Count)" + +# ---------------------------------------------------- +# 3. For each GPO, get an XML report and pull RestrictedGroup nodes +# ---------------------------------------------------- + +Add-Content -Path $OutputFile -Value " configured_gpos:" +foreach ($gpoGuid in $gpoLinks.Keys) { + try { + $gpo = Get-GPO -Guid $gpoGuid -ErrorAction Stop + } + catch { + Write-Warning "Unable to retrieve GPO $gpoGuid – skipping." + continue + } + Write-Host "GPO: $($gpo.DisplayName)" + Add-Content -Path $OutputFile -Value " $($gpo.DisplayName):" + + # Pull the XML report + try { + $xmlString = Get-GPOReport -Guid $gpoGuid -ReportType Xml -ErrorAction Stop + } + catch { + Write-Warning "Unable to generate XML report for $($gpo.DisplayName)." + continue + } + + $xml = [xml]$xmlString + + $ns = New-Object System.Xml.XmlNamespaceManager ($xml.NameTable) + $ns.AddNamespace('m', 'http://www.microsoft.com/GroupPolicy/Settings') # default namespace + $ns.AddNamespace('q1', 'http://www.microsoft.com/GroupPolicy/Settings/Security') + $ns.AddNamespace('t', 'http://www.microsoft.com/GroupPolicy/Types') + $ns.AddNamespace('xsi','http://www.w3.org/2001/XMLSchema-instance') + + # Find every element + $restrictedGroups = $xml.SelectNodes('//q1:RestrictedGroups', $ns) + + # Loop and build the string + foreach ($rg in $restrictedGroups) { + $adGroupName = ($rg.SelectSingleNode('q1:GroupName/t:Name', $ns)).InnerText + $localGroupName = ($rg.SelectSingleNode('q1:Memberof/t:Name', $ns)).InnerText + $plainAD_Groupname = ($adGroupName -split '\\')[-1] + Write-Output "AD Group: $adGroupName - Local Group: $localGroupName" + Add-Content -Path $OutputFile -Value " - ad_group: $plainAD_Groupname" + Add-Content -Path $OutputFile -Value " local_group: $localGroupName" + } + Add-Content -Path $OutputFile -Value "" +} +Add-Content -Path $OutputFile -Value "..." diff --git a/tasks/api.yaml b/tasks/api.yaml new file mode 100644 index 0000000..738e96f --- /dev/null +++ b/tasks/api.yaml @@ -0,0 +1,72 @@ +--- + +- name: API Service - file and folder handler + block: + + - name: API Service - create python service folder + file: + path: "{{ api_service_folder }}" + state: directory + owner: "{{ service_user }}" + group: "{{ service_user }}" + mode: '0755' + + - name: API Service - template app.py + template: + src: app.py + dest: "{{ api_service_folder }}/app.py" + owner: "{{ service_user }}" + group: "{{ service_user }}" + mode: 0755 + +# Create python service venv +- name: API Service - Python venv handler + block: + + - name: API Service - create python venv requirement file + copy: + dest: "{{ api_service_folder }}/requirements.txt" + content: | + flask + pytz + requests + opencv-python + pyyaml + owner: "{{ service_user }}" + group: "{{ service_user }}" + mode: 0644 + + - name: API Service - build python venv + pip: + virtualenv: "{{ api_service_folder }}/venv" + requirements: "{{ api_service_folder }}/requirements.txt" + virtualenv_command: python3 -m venv + state: present + +- name: API Service - api service handler + block: + + - name: "API Service - {{ api_service_name }} - stop service if running" + ignore_errors: yes + systemd: + name: "{{ api_service_name }}.service" + state: stopped + + - name: "API Service - template {{ api_service_name }}.service" + vars: + service_name: "{{ api_service_name }}" + service_working_folder: "{{ api_service_folder }}" + service_exe: "{{ api_service_folder }}/venv/bin/python -u {{ api_service_folder }}/app.py" + template: + src: "service_template.service" + dest: "/etc/systemd/system/{{ api_service_name }}.service" + mode: 0644 + + - name: "API Service - {{ api_service_name }} - enable and start service" + systemd: + name: "{{ api_service_name }}.service" + state: started + enabled: yes + daemon_reload: yes + +... \ No newline at end of file diff --git a/tasks/dashboard.yaml b/tasks/dashboard.yaml new file mode 100644 index 0000000..50fa7f3 --- /dev/null +++ b/tasks/dashboard.yaml @@ -0,0 +1,39 @@ +--- +############################################### +# This part sets up drive history dashboard +############################################### + +# Create docker Folder +- name: drive history dashboard - create dashboard_web_root folder + file: + path: "{{ dashboard_web_root }}" + state: directory + mode: '0755' + owner: root + group: root + +- name: drive history dashboard - copy files for docker container + copy: + src: "dashboard/" + dest: "{{ dashboard_web_root }}/html" + mode: 0755 + owner: root + group: root + +- name: docker container + block: + + - name: service_control_website - template docker-compose.yaml + template: + src: docker-compose-php.yaml + dest: "{{ dashboard_web_root }}/docker-compose.yaml" + mode: 0644 + + - name: "service_control_website - Start container at {{ container_http_port }}" + shell: "docker-compose -f {{ dashboard_web_root }}/docker-compose.yaml up -d" + register: docker_output + - debug: | + msg="{{ docker_output.stdout_lines }}" + msg="{{ docker_output.stderr_lines }}" + +... \ No newline at end of file diff --git a/tasks/main.yaml b/tasks/main.yaml new file mode 100644 index 0000000..36df758 --- /dev/null +++ b/tasks/main.yaml @@ -0,0 +1,23 @@ +--- + +# Mount GPO Share path +#- name: Mount GPO Share path +# include_role: +# name: "cifs_mount" +# vars: +# smb_username: "{{ ITMFG_USERNAME }}" +# smb_password: "{{ ITMFG_PASSWORD }}" +# server_path: "//172.23.1.23/gpo" +# target_path: "{{ gpo_yaml_share }}" + +# create python gpo service +#- name: GPO Site - python api service +# include_tasks: api.yaml + +# create python gpo service +- name: GPO Site - dashboard container + include_tasks: dashboard.yaml + + + +... \ No newline at end of file diff --git a/templates/app.py b/templates/app.py new file mode 100644 index 0000000..20e178e --- /dev/null +++ b/templates/app.py @@ -0,0 +1,65 @@ +import yaml +from flask import Flask, request, jsonify + +app = Flask(__name__) + +# Load GPO data +with open('{{ gpo_yaml_path }}', 'r') as file: + data = yaml.safe_load(file) + +# Debugging output to check the structure of loaded data +# print("Loaded YAML data:", data) + +@app.route('/gpos', methods=['GET']) +def get_gpos(): + group_name = request.args.get('group_name') + + if not group_name: + return jsonify({"error": "Group name is required"}), 400 + + gpos = [] + for gpo, links in data['configured_gpos'].items(): + for link in links: + if link['ad_group'] == group_name or link['local_group'] == group_name: + gpos.append(gpo) + break + + return jsonify({"group_name": group_name, "GPOs": gpos}) + +@app.route('/gpo', methods=['GET']) +def get_groups(): + group_name = request.args.get('group') + print("Group specified:", group_name) + if not group_name: + return jsonify({"error": "Group parameter is required"}), 400 + + gpos = [] + for entry in data: + configured_gpos = entry.get('configured_gpos', {}) + for gpo, details in configured_gpos.items(): + if details is None: + continue + for detail in details: + if detail['ad_group'] == group_name: + gpos.append({ + "gpo": gpo, + "local_group": detail['local_group'], + "ad_group": detail['ad_group'] + }) + return jsonify(gpos) + +# test route +@app.route('/test', methods=['GET']) +def test(): + try: + root_ou = data[0]['root_ou'] + except KeyError: + # Handle the case where 'root_ou' does not exist in the dictionary + return jsonify({"error": "KeyError: 'root_ou' not found in provided data"}), 400 + except TypeError: + # Handle cases where data might be of a different type than expected + return jsonify({"error": "TypeError: Data is not a dictionary"}), 400 + return jsonify({"result": "test", "OU_Root": root_ou}) + +if __name__ == '__main__': + app.run(debug=True, host='{{ api_service_bind_ip }}', port={{ api_service_port }}) \ No newline at end of file diff --git a/templates/docker-compose-php.yaml b/templates/docker-compose-php.yaml new file mode 100644 index 0000000..076c1d8 --- /dev/null +++ b/templates/docker-compose-php.yaml @@ -0,0 +1,12 @@ +services: + + {{ container_service_name }}: + container_name: {{ container_name }} + image: php:8.0-apache + ports: + - {{ container_http_port }}:80 + volumes: + - ./html:/var/www/html/ + {{ extra_volumes }} + network_mode: bridge + restart: always diff --git a/templates/service_template.service b/templates/service_template.service new file mode 100644 index 0000000..6f5ef22 --- /dev/null +++ b/templates/service_template.service @@ -0,0 +1,14 @@ + +[Unit] +Description={{ service_name }} +After=network.target + +[Service] +User=root +Group=root +WorkingDirectory={{ service_working_folder }} +ExecStart={{ service_exe }} +Restart=always + +[Install] +WantedBy=multi-user.target