Blog History

January 7, 2021

Cross forest Domain Admins GPO

This process will enable the Domain Admins group from one forest to get added to the local Administrators group on servers in another forest with a one way, external forest trust in place.

Here is a basic breakdown [1]:

  • Domain Admins is a Global Group and thus confined to their own domain, so you must nest them inside of a Domain LOCAL group inside of the target forest.
  • Universal groups are used to consolidate groups that span domains inside of a forest, and in my use case, my domain is intentionally in another forest as I want the domains to stay divided.
  • Global groups may contain accounts and other global groups from the SAME domain.
  • Domain local groups may contain accounts, global groups, universal groups from ANY trusted domain, as well as domain local groups from the same domain.

The order of this nesting concept is AGDLP [2]: Account > Global(domain1) > Domain Local(domain2) > Permission.

This new group, in my example is "Group-Server-Admins". Once this is done, we can now create the GPO to push to the target domain's servers local Administrators group. I have applied this at the domain root as I want all of domain1's Domain Admins group to have local Administrator access on domain2's servers.

Path as follows: Computer>Preferences>Control Panel>Local Users and Groups


 

 

 

 

References:

[1] https://docs.microsoft.com/en-us/windows/security/identity-protection/access-control/active-directory-security-groups 

[2] https://en.wikipedia.org/wiki/AGDLP

 

January 6, 2021

Azure SQL permissions

Using Azure console to give IAM roles only gives them console administrative permissions, it does not give those users permissions inside of SQL. You must create these users and permissions manually [1].

 


 

 

 

 

 

To view the permissions given to the users:

SELECT DP1.name AS DatabaseRoleName,  
   isnull (DP2.name, 'No members') AS DatabaseUserName  
 FROM sys.database_role_members AS DRM
 RIGHT OUTER JOIN sys.database_principals AS DP1
   ON DRM.role_principal_id = DP1.principal_id
 LEFT OUTER JOIN sys.database_principals AS DP2
   ON DRM.member_principal_id = DP2.principal_id
WHERE DP1.type = 'R'
ORDER BY DP1.name;

To create SQL logins:
#run on master
CREATE USER [user@domain.com] 
FROM EXTERNAL PROVIDER 
ALTER ROLE dbmanager ADD MEMBER [user@domain.com] 
ALTER ROLE loginmanager ADD MEMBER [user@domain.com] 

To give users database permissions:
#run on DB
CREATE USER [user@domain.com] 
FROM EXTERNAL PROVIDER 
ALTER ROLE db_datareader ADD MEMBER [user@domain.com] 
ALTER ROLE db_datawriter ADD MEMBER [user@domain.com] 
ALTER ROLE db_owner ADD MEMBER [user@domain.com] 

The users might not need db_owner, depending on what they are trying to do to the database.

References:

[1] https://www.mssqltips.com/sqlservertip/5242/adding-users-to-azure-sql-databases/

December 31, 2020

AWS Secrets Manager password retrieval via BASH script

Basic test script for retrieving a password [1] from AWS Secrets Manager. One can obviously use the password variable for an actual operation instead of echoing the password.

You will need a IAM role with the SecretsManagerReadWrite [2] policy attached to it. Configure the AWS CLI with the user keys.

Initially I attempted to use the native AWS CLI --query option [3], but it spit out the username as well as some punctiation that was not needed. To get around this, I used "jq" to parse the JSON results and spit out just the password.

Modified from the following resources [4-6]

#!/bin/bash

testuser_pw="$(aws secretsmanager get-secret-value --secret-id testsecret
| jq --raw-output '.SecretString' | jq -r .testuser)"

echo $testuser_pw

Here is a screenshot of the 3 stages of testing, with the last line finally outputting just the password as desired.

 

 

 

 

References:

[1] https://docs.aws.amazon.com/cli/latest/reference/secretsmanager/get-secret-value.html
[2] https://docs.aws.amazon.com/secretsmanager/latest/userguide/reference_available-policies.html
[3] https://docs.aws.amazon.com/cli/latest/userguide/cli-usage-output.html#cli-usage-output-filter
[4] https://stackoverflow.com/questions/50911540/parsing-secrets-from-aws-secrets-manager-using-aws-cli
[5] https://stackoverflow.com/questions/36452555/bash-script-to-loop-through-output-from-aws-command-line-client
[6] https://stackoverflow.com/questions/44296729/aws-cli-command-inside-bash-script-cant-locate-file

 

December 29, 2020

AWS EC2 Stop/Start BASH script for end users to run on Linux or Mac

You must first provision an IAM user with permissions to perform the action:

{
    "Version""2012-10-17",
    "Statement": [
        {
            "Effect""Allow",
            "Action": [
                "ec2:StartInstances",
                "ec2:StopInstances",
            ],
            "Resource": [
                "arn:aws:ec2:us-east-1:<account number>:instance/i-1234567890"
            ]
        }
    ]
}

Configure the AWS CLI with the above IAM user on the end users local computer.

When they run the script, they will need to specify one of three options: status, start, or stop 

If writing this script in Windows, and trying to test in WSL or an actual Unix based system, then you might get this error:

'\r'command not found

Use "dos2unix" on the script file after each edit to modify the newline characters so they are Unix compatible [1].

Modified from source [2]

#!/bin/bash

INSTANCE_ID="i-1234567890"

function _log(){
    trailbefore=$2
    start=""
    if [ -z $trailbefore ] || [ $trailbefore = true ]
        then
        start=" - "
    fi

    printf "$start$1"
}

function run_command (){
    COMMAND=$1
    # note in the original parameter count as new parameter
    QUERY="$2 $3"
    OUTPUT="--output text"
    local result=$(eval aws ec2 $COMMAND --instance-ids $INSTANCE_ID $QUERY $OUTPUT)
    echo "$result"
}

function getStatus(){
    CMD="describe-instances"
    EXTRA="--query \"Reservations[].Instances[].State[].Name\""
    result=$(run_command $CMD $EXTRA)
    echo $result
}

function _checkStatus(){
    status=$(getStatus)
    if [ $status = "pending" ] || [ $status = "stopping" ]
        then
        _log "Current status: $status"
        _log " Wating "
        while [ $status = "pending" ] || [ $status = "stopping" ]
            do
            sleep 5
            _log "." false
            status=$(getStatus)
        done
        _log "\n" false
    fi
}

function start {
    CMD="start-instances"
    _checkStatus
    result=$(run_command $CMD)
    echo $result
}
function stop {
    CMD="stop-instances"
    _checkStatus
    result=$(run_command $CMD)
    echo $result
}

if [ -z "$1" ]
    then
    _log "\n Possible commands: status|start|stop \n\n"
else
    if [ $1 = "start" ] 
        then
        start
    elif [ $1 = "stop" ] 
        then
        stop
    elif [ $1 = "status" ] 
        then
        getStatus   
    fi
fi

[1] https://stackoverflow.com/questions/11616835/r-command-not-found-bashrc-bash-profile

[2] https://stackoverflow.com/questions/42641970/aws-cli-bash-script-to-manage-instances


 

October 14, 2020

AWS VPC Terraform automation

I have wanted to learn Terraform for a awhile now, and finally had the business opportunity last night/today to bury my head in the docs to learn the basics. Was an absolute blast, and now I'm hooked with the idea of automating everything. Plans are to research how to utilize Terraform, Ansible and Pulumi into a cohesive strategy. Stay tuned as I learn and post more. I still need to learn many things, such as securing secrets, importing existing infrastructure, launching differences resources and working with on premise equipment, ect. But the plan is to have pre-built templates for every new client, with minimal reworking of the code via the variables file.

Two brief observations:

  • As mentioned above, Terraform can utilize separate variable files which is fantastic. You will see multiple references to "var.<variable name>".  Those files just reside in the same folder as the config file, and then to use that variables file --> terraform apply -var-file="abc.tfvars" 
  • I really did not want to rework every CIDR statement in the code for every new client, so declaring the VPC CIDR in the variables file enables me to then use cidrsubnet to auto segment. See the link for how it works. https://www.terraform.io/docs/configuration/functions/cidrsubnet.html. I still want to make a loop for the subnets instead of declaring each one, but that'll be for a later date.
Below is my code (50% me, 50% forums/docs). There are many great guides/videos on setting up Terraform and how things work, but feel free to reach out to me on Twitter or LinkedIn if you have questions.  
 
provider "aws" {
# Here you can use hard coded credentials, or AWS CLI profile credentials 
  access_key = "xxxx"
  secret_key = "xxxx"
#profile = "TerraformDemo" 
  region     = var.region
}

# Create VPC
resource "aws_vpc" "Main" {
    cidr_block = var.vpc_cidr
    instance_tenancy = "default"
    enable_dns_hostnames = true
    enable_dns_support = true
    tags = {
          Name = "Main VPC"
    }
}

#Create PRIVATE Subnets
resource "aws_subnet" "PRIVATE-1" {
  vpc_id     = aws_vpc.Main.id
  cidr_block = cidrsubnet(var.vpc_cidr30)
  availability_zone = "us-east-1a"

  tags = {
    Name = "PRIVATE 1"
  }
}
resource "aws_subnet" "PRIVATE-2" {
  vpc_id     = aws_vpc.Main.id
  cidr_block = cidrsubnet(var.vpc_cidr31)
  availability_zone = "us-east-1b"

  tags = {
    Name = "PRIVATE 2"
  }
}
resource "aws_subnet" "PRIVATE-3" {
  vpc_id     = aws_vpc.Main.id
  cidr_block = cidrsubnet(var.vpc_cidr32)
  availability_zone = "us-east-1c"

  tags = {
    Name = "PRIVATE 3"
  }
}
resource "aws_subnet" "PRIVATE-4" {
  vpc_id     = aws_vpc.Main.id
  cidr_block = cidrsubnet(var.vpc_cidr33)
  availability_zone = "us-east-1d"

  tags = {
    Name = "PRIVATE 4"
  }
}

#Create PUBLIC Subnets
resource "aws_subnet" "PUBLIC-1" {
  vpc_id     = aws_vpc.Main.id
  cidr_block = cidrsubnet(var.vpc_cidr34)
  availability_zone = "us-east-1a"
  map_public_ip_on_launch = true
  
  tags = {
    Name = "PUBLIC 1"
  }
}
resource "aws_subnet" "PUBLIC-2" {
  vpc_id     = aws_vpc.Main.id
  cidr_block = cidrsubnet(var.vpc_cidr35)
  availability_zone = "us-east-1b"
  map_public_ip_on_launch = true
  
  tags = {
    Name = "PUBLIC 2"
  }
}
resource "aws_subnet" "PUBLIC-3" {
  vpc_id     = aws_vpc.Main.id
  cidr_block = cidrsubnet(var.vpc_cidr36)
  availability_zone = "us-east-1c"
  map_public_ip_on_launch = true
  
  tags = {
    Name = "PUBLIC 3"
  }
}
resource "aws_subnet" "PUBLIC-4" {
  vpc_id     = aws_vpc.Main.id
  cidr_block = cidrsubnet(var.vpc_cidr37)
  availability_zone = "us-east-1d"
  map_public_ip_on_launch = true
  
  tags = {
    Name = "PUBLIC 4"
  }
}

#Create IGW and attach to VPC
resource "aws_internet_gateway" "IGW" {
  vpc_id = aws_vpc.Main.id

  tags = {
    Name = "IGW"
  }
}

#Allocate EIP for NAT Gateway
resource "aws_eip" "NATGW-EIP" {
  vpc      = true
  tags = {
    Name = "NATGW-EIP"

  }
}

#Create NAT Gateway in Public PUBLIC 4
resource "aws_nat_gateway" "NATGW" {
  allocation_id = aws_eip.NATGW-EIP.id
  subnet_id     = aws_subnet.PUBLIC-4.id

  tags = {
    Name = "NATGW"
  }
}

#Create peering connnection
resource "aws_vpc_peering_connection" "peer1" {
  peer_owner_id = "xxxx"
  peer_vpc_id = "vpc-xxxx"
  vpc_id      = aws_vpc.Main.id
  peer_region   = "us-east-1"
  tags = {
    Name = "Peer #1"
  }
}

#Create Public Route Table
resource "aws_route_table" "PUBLIC-RT" {
  vpc_id = aws_vpc.Main.id

  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.IGW.id
  }

  route {
    cidr_block = "x.x.x.x/24"
    vpc_peering_connection_id = aws_vpc_peering_connection.peer1.id
  }

    tags = {
    Name = "PUBLIC-RT"
  }
}

#Associate Public Subnets to Public Route Table
resource "aws_route_table_association" "PUBLIC-1-PUBLIC-RT" {
  subnet_id      = aws_subnet.PUBLIC-1.id
  route_table_id = aws_route_table.PUBLIC-RT.id
}
resource "aws_route_table_association" "PUBLIC-2-PUBLIC-RT" {
  subnet_id      = aws_subnet.PUBLIC-2.id
  route_table_id = aws_route_table.PUBLIC-RT.id
}
resource "aws_route_table_association" "PUBLIC-3-PUBLIC-RT" {
  subnet_id      = aws_subnet.PUBLIC-3.id
  route_table_id = aws_route_table.PUBLIC-RT.id
}
resource "aws_route_table_association" "PUBLIC-4-PUBLIC-RT" {
  subnet_id      = aws_subnet.PUBLIC-4.id
  route_table_id = aws_route_table.PUBLIC-RT.id
}

#Create Private Route Table
resource "aws_route_table" "PRIVATE-RT" {
  vpc_id = aws_vpc.Main.id

  route {
    cidr_block = "0.0.0.0/0"
    nat_gateway_id = aws_nat_gateway.NATGW.id
  }

  route {
    cidr_block = "x.x.x.x/24"
    vpc_peering_connection_id = aws_vpc_peering_connection.peer1.id
  }

  tags = {
    Name = "PRIVATE-RT"
  }
}

#Associate Private Subnets to Private Route Table
resource "aws_route_table_association" "PRIVATE-1-PRIVATE-RT" {
  subnet_id      = aws_subnet.PRIVATE-1.id
  route_table_id = aws_route_table.PRIVATE-RT.id
}
resource "aws_route_table_association" "PRIVATE-2-PRIVATE-RT" {
  subnet_id      = aws_subnet.PRIVATE-2.id
  route_table_id = aws_route_table.PRIVATE-RT.id
}
resource "aws_route_table_association" "PRIVATE-3-PRIVATE-RT" {
  subnet_id      = aws_subnet.PRIVATE-3.id
  route_table_id = aws_route_table.PRIVATE-RT.id
}
resource "aws_route_table_association" "PRIVATE-4-PRIVATE-RT" {
  subnet_id      = aws_subnet.PRIVATE-4.id
  route_table_id = aws_route_table.PRIVATE-RT.id
}

#Create DHCP Options Set
resource "aws_vpc_dhcp_options" "domain" {
  domain_name          = "domain.local"
  domain_name_servers  = ["x.x.x.x""x.x.x.x"]
  ntp_servers          = ["x.x.x.x"]
}

#Create OpenVPN SG
resource "aws_security_group" "OpenVPN" {
  vpc_id = aws_vpc.Main.id
  name = "OpenVPN Access Group"
  description = "OpenVPN Access Group"
    ingress {
    from_port = 943
    to_port = 943
    protocol = "tcp"
    cidr_blocks = ["x.x.x.x/32"]
    description = "OpenVPN admin"
  }    
  ingress {
    from_port = 1194
    to_port = 1194
    protocol = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
  ingress {
    from_port = 443
    to_port = 443
    protocol = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
  egress {
    from_port = 0
    to_port = 0
    protocol = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}