<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Nicholas Drone</title>
    <description>The latest articles on DEV Community by Nicholas Drone (@ndrone).</description>
    <link>https://dev.to/ndrone</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F6521%2F0e139613-3893-4891-96f7-384fe6c1856e.jpg</url>
      <title>DEV Community: Nicholas Drone</title>
      <link>https://dev.to/ndrone</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ndrone"/>
    <language>en</language>
    <item>
      <title>Automated Service Principal Password Rotation</title>
      <dc:creator>Nicholas Drone</dc:creator>
      <pubDate>Thu, 04 Nov 2021 19:53:26 +0000</pubDate>
      <link>https://dev.to/ndrone/automated-service-principal-password-rotation-571f</link>
      <guid>https://dev.to/ndrone/automated-service-principal-password-rotation-571f</guid>
      <description>&lt;h2&gt;
  
  
  Assumptions
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Knowledge of Terraform&lt;/li&gt;
&lt;li&gt;Knowledge of using Terraform with Azure&lt;/li&gt;
&lt;li&gt;Knowledge of GitHub Actions&lt;/li&gt;
&lt;li&gt;Knowledge of Bash Scripting&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Setup
&lt;/h2&gt;

&lt;p&gt;This code block holds all the Azure resources built out by Terraform. The resource group and key vault are not required. It's only to show the storage of the service principal password and to show that when a terraform apply -replace is done that whatever is consuming the output of that resource is also updated. After the resource group and key vault are two service principals examples for this example.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;terraform {
  backend "azurerm" {
    subscription_id      = "xxx-xxx-xxx-xxx"
    resource_group_name  = "tfstate"
    storage_account_name = "dronetfstate"
    container_name       = "tfstate"
    key                  = "sp_rotate.tfstate"
  }
  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "2.83.0"
    }
    azuread = {
      source  = "hashicorp/azuread"
      version = "2.8.0"
    }
  }
}

provider "azurerm" {
  features {}
}

provider "azuread" {
}

resource "azurerm_resource_group" "example" {
  name     = "example"
  location = "Eastus2"
}

data "azurerm_client_config" "current" {}

resource "azurerm_key_vault" "example" {
  name                        = "droneexamplekeyvault"
  location                    = azurerm_resource_group.example.location
  resource_group_name         = azurerm_resource_group.example.name
  enabled_for_disk_encryption = true
  tenant_id                   = data.azurerm_client_config.current.tenant_id
  soft_delete_retention_days  = 7
  purge_protection_enabled    = false

  sku_name = "standard"

  access_policy {
    tenant_id = data.azurerm_client_config.current.tenant_id
    object_id = data.azurerm_client_config.current.object_id

    secret_permissions = [
      "Get",
      "List",
      "Set"
    ]
  }
}

data "azuread_client_config" "current" {}

resource "azuread_application" "example1" {
  display_name = "example1"
  owners       = [data.azuread_client_config.current.object_id]
}

resource "azuread_service_principal" "example1" {
  application_id               = azuread_application.example1.application_id
  app_role_assignment_required = false
  owners                       = [data.azuread_client_config.current.object_id]
}

resource "azuread_service_principal_password" "example1" {
  service_principal_id = azuread_service_principal.example1.object_id
}

resource "azurerm_key_vault_secret" "example1" {
  name         = "example1"
  value        = azuread_service_principal_password.example1.value
  key_vault_id = azurerm_key_vault.example.id
}

resource "azuread_application" "example2" {
  display_name = "example2"
  owners       = [data.azuread_client_config.current.object_id]
}

resource "azuread_service_principal" "example2" {
  application_id               = azuread_application.example2.application_id
  app_role_assignment_required = false
  owners                       = [data.azuread_client_config.current.object_id]
}

resource "azuread_service_principal_password" "example2" {
  service_principal_id = azuread_service_principal.example2.object_id
}

resource "azurerm_key_vault_secret" "example2" {
  name         = "example2"
  value        = azuread_service_principal_password.example2.value
  key_vault_id = azurerm_key_vault.example.id
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When having service principals created its good security practice to rotate those passwords on a regular basis. If you are using GitHub Actions to already apply/execute your resource changes, then adding a workflow to then rotate the password by doing forcing a replacement of the password resource causes a new password to be generated.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rotation Workflow
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;name: Rotate Secrets

on:
  schedule:
    - cron: '0 1 1 12/3 *' # run every 4 months.
  workflow_dispatch:

env:
  ARM_SUBSCRIPTION_ID: ${{ secrets.ARM_SUBSCRIPTION_ID }}
  ARM_CLIENT_ID: ${{ secrets.ARM_CLIENT_ID }}
  ARM_CLIENT_SECRET: ${{ secrets.ARM_CLIENT_SECRET }}
  ARM_TENANT_ID: ${{ secrets.ARM_TENANT_ID }}
  TF_IN_AUTOMATION: true

jobs:
  replace:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v2
      - name: Setup Terraform
        uses: hashicorp/setup-terraform@v1
        with:
          terraform_version: 1.0.8
      - name: Terraform Init
        run: terraform init -input=false -no-color
        # Pull all the resources from the state file put them in a file for the next step
      - name: Terraform List State Resources
        run: terraform state list &amp;gt; stateList
        # We are going to loop through each line in the file of the resources
        # We only want to replace the service_principal_password resource, so we
        # need to check the start of each resource starts with the correct address.
      - name: Terraform Replace
        run: while read target; do if [[ "${target:0:34}" == "azuread_service_principal_password" ]]; then terraform apply -replace="$target" -input=false -no-color -auto-approve; fi; done &amp;lt; stateList
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The last two steps are where the work is really done. First we want to create a temporary file to store all the resources within the state file. Echoing out the created file&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;data.azuread_client_config.current
data.azurerm_client_config.current
azuread_application.example1
azuread_application.example2
azuread_service_principal.example1
azuread_service_principal.example2
azuread_service_principal_password.example1
azuread_service_principal_password.example2
azurerm_key_vault.example
azurerm_key_vault_secret.example1
azurerm_key_vault_secret.example2
azurerm_resource_group.example
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then the following step we are iterating over all those resources and looking for the ones with the block label of &lt;code&gt;azuread_service_principal_password&lt;/code&gt;. When we find those block labels we then want to pass the entire address to Terraform to force  a replacement of those resources.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;while read target; do 
    if [[ "${target:0:34}" == "azuread_service_principal_password" ]]; then 
        terraform apply -replace="$target" -input=false -no-color -auto-approve
    fi
done &amp;lt; stateList
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>terraform</category>
      <category>azure</category>
      <category>github</category>
      <category>devops</category>
    </item>
    <item>
      <title>Repositories are not just for Databases</title>
      <dc:creator>Nicholas Drone</dc:creator>
      <pubDate>Thu, 14 Nov 2019 23:01:32 +0000</pubDate>
      <link>https://dev.to/ndrone/repositories-are-not-just-for-databases-4a85</link>
      <guid>https://dev.to/ndrone/repositories-are-not-just-for-databases-4a85</guid>
      <description>&lt;p&gt;This blog post came out of the necessity of my own mistake. I have been working strictly with databases longer than I care to admit to. So in a project, we were making changes so that a specific application didn't have direct database access to a different application. Simple separation of concerns right, and microservice architecture. All that jazz right? So I brought in an HttpClient into the service layer, removing the repository. It seemed right at the time. Not sure why, several weeks later this pattern was copied by may other developers on the project, and just turn that project to no longer a clean looking platform of microservices. Then one day it hit me. The HTTP crud operations were doing the same thing as the database operations. And both should be treated as a datastore. Whether the data lives in a file of any structure, HTTP request, and or database. The access to that data should all live in the repository layer. &lt;/p&gt;

&lt;p&gt;The code in this blog can be seen &lt;a href="https://github.com/ndrone/repositoryisnotjustdatabase" rel="noopener noreferrer"&gt;here&lt;/a&gt;. The git repository as of writing this post contains a library module with several sub-modules. The idea is that the libraries are then used by the applications that have yet to be written to further explain why repositories are for &lt;strong&gt;datastores&lt;/strong&gt; not just databases. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fndrone%2Fdev.to%2Fmaster%2Fblog-posts%2Frepositories-are-not-just-for-db%2Ftps%3A%2F%2Fraw.githubusercontent.com%2Fndrone%2Fdev.to%2Fmaster%2Fblog-posts%2Frepositories-are-not-just-for-db%2Fassets%2FlibraryModules.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fndrone%2Fdev.to%2Fmaster%2Fblog-posts%2Frepositories-are-not-just-for-db%2Ftps%3A%2F%2Fraw.githubusercontent.com%2Fndrone%2Fdev.to%2Fmaster%2Fblog-posts%2Frepositories-are-not-just-for-db%2Fassets%2FlibraryModules.png" alt="Image of library modules"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's ignore the contact-core-test module for now. It contains a base test class. Each ContactRepository implementation test class can extend the base test class, and then only needs to write tests based on their implementation, but should pass all tests in the base class. I may turn that into a blog post of its own.&lt;/p&gt;

&lt;p&gt;The contact-core module. This is a small simple module that contains the contact object. The repository interface for the contact object. The exception class for that object, and the validator class. The validator class just validates that the inputs specified in the methods of the interface adhere to the specifications of the javadocs of the interface.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;package&lt;/span&gt; &lt;span class="nx"&gt;org&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;example&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * A simple ContactRepository. Any implementation of this interface should
 * leverage {@link ContactRepositoryValidator} to validate the overriding methods
 * arguments. The implementations Tests classes should extend ContactRepositoryTests
 *
 * @see contact-core-test module
 */&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;ContactRepository&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="cm"&gt;/**
   * Search for the contact with the existing &amp;lt;code&amp;gt;id&amp;lt;/code&amp;gt;
   *
   * @param id of the contact to be searched for
   * @return &amp;lt;code&amp;gt;contact&amp;lt;/code&amp;gt; maybe &amp;lt;code&amp;gt;null&amp;lt;/code&amp;gt; if &amp;lt;code&amp;gt;contact&amp;lt;/code&amp;gt;
   * with the &amp;lt;code&amp;gt;id&amp;lt;/code&amp;gt; doesn't exist
   * @throws IllegalArgumentException if &amp;lt;code&amp;gt;id&amp;lt;/code&amp;gt; is &amp;lt;code&amp;gt;null&amp;lt;/code&amp;gt;
   * @throws ContactException         if an error has occurred
   */&lt;/span&gt;
  &lt;span class="nx"&gt;Contact&lt;/span&gt; &lt;span class="nf"&gt;findContact&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Integer&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;throws&lt;/span&gt; &lt;span class="nx"&gt;IllegalArgumentException&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ContactException&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="cm"&gt;/**
   * Searching for &amp;lt;code&amp;gt;contact&amp;lt;/code&amp;gt; that exactly matches the parameters
   * passed into the method.
   *
   * @param firstName to be exactly searched with
   * @param lastName  to be exactly searched with
   * @return a collection of &amp;lt;code&amp;gt;contact&amp;lt;/code&amp;gt; that match the parameters
   * @throws IllegalArgumentException if both parameters are &amp;lt;code&amp;gt;null&amp;lt;/code&amp;gt;
   * @throws ContactException         if an error has occurred
   */&lt;/span&gt;
  &lt;span class="nx"&gt;Iterable&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Contact&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;findContacts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt; &lt;span class="nx"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt; &lt;span class="nx"&gt;lastName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="nx"&gt;throws&lt;/span&gt; &lt;span class="nx"&gt;IllegalArgumentException&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ContactException&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="cm"&gt;/**
   * Saves a new or updated contact information. If a &amp;lt;code&amp;gt;contact&amp;lt;/code&amp;gt; is passed with the
   * &amp;lt;code&amp;gt;id&amp;lt;/code&amp;gt; is &amp;lt;code&amp;gt;null&amp;lt;/code&amp;gt;. The contact will be treated as a new contact, and
   * an &amp;lt;code&amp;gt;id&amp;lt;/code&amp;gt; will be generated for it. If an &amp;lt;code&amp;gt;contact&amp;lt;/code&amp;gt; has an existing
   * &amp;lt;code&amp;gt;id&amp;lt;/code&amp;gt; the &amp;lt;code&amp;gt;contact&amp;lt;/code&amp;gt; with be updated.
   *
   * @param contact to be saved or updated
   * @return saved &amp;lt;code&amp;gt;contact&amp;lt;/code&amp;gt;
   * @throws IllegalArgumentException if a &amp;lt;code&amp;gt;contact&amp;lt;/code&amp;gt; with an existing &amp;lt;code&amp;gt;id&amp;lt;/code&amp;gt;
   *                                  can not be found.
   * @throws ContactException         if an error has occurred
   */&lt;/span&gt;
  &lt;span class="nx"&gt;Contact&lt;/span&gt; &lt;span class="nf"&gt;saveContact&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Contact&lt;/span&gt; &lt;span class="nx"&gt;contact&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;throws&lt;/span&gt; &lt;span class="nx"&gt;IllegalArgumentException&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ContactException&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The contact-file, contact-http, and contact-jdbc are all modules that implement the &lt;code&gt;ContactRepository&lt;/code&gt;. So an application then can bring in the specific technology implementation and build an application based on the interface. &lt;/p&gt;

&lt;h2&gt;
  
  
  A hypothetical scenario
&lt;/h2&gt;

&lt;p&gt;As a product team, we are spinning up a contact service. We've requested a database, but that won't be ready for weeks, and the boss wants it done by next week. Well, we still can write that service. The rest application is written to the &lt;code&gt;ContactRepository&lt;/code&gt; interface. We bring in the contact-file dependency for the short term while we are waiting on our database to be complete. Once the database is complete we then can swap out the contact-file dependency for the jdbc one, only changing enough code to connect to the database. Then file contents are moved to the database after deployment. The boss shows off your work to another team that wants access to the contact info. Except they can't get past the firewall use the contact-jdbc implementation. Not that you wanted them to. So you create the HTTP implementation to your applications URL endpoints. Then they just need to write code to the interface you just did, but leverage HTTP to get the same data you got from the JDBC implementation. The good news is if your API change, you can control the HTTP implementation too, so you can save your customer the headache of handling the versioning, just give them the new HTTP jar with the versioned changes in it.&lt;/p&gt;

</description>
      <category>oop</category>
    </item>
  </channel>
</rss>
