[{"content":"","date":"9 April 2026","externalUrl":null,"permalink":"/","section":"Just Tinkering","summary":"","title":"Just Tinkering","type":"page"},{"content":"","date":"9 April 2026","externalUrl":null,"permalink":"/tags/mental-health/","section":"Tags","summary":"","title":"Mental Health","type":"tags"},{"content":"","date":"9 April 2026","externalUrl":null,"permalink":"/tags/personal/","section":"Tags","summary":"","title":"Personal","type":"tags"},{"content":"A lot has happened the last 12 months. My dad\u0026rsquo;s health deteriorated more rapidly, my mom had some injuries which impeded on her mobility, and last January, my dad died as a result of COPD and pulmonary fibrosis.\nWe\u0026rsquo;ve been able to spend a lot of time together in those last months. And although I\u0026rsquo;m glad that he no longer has to fight for every breath, it leaves a gaping hole, which feels will never fill again. Currently the edges are still sharp, but here and there, they begin to soften, and some plants begin to grow around it.\nThis also had, and has, an effect on my depression. I do feel, however, that currently my depression isn\u0026rsquo;t ruling my life, but I\u0026rsquo;m experiencing it. I can still enjoy things, like sunny days, spending time with friends, spamming others with reels on social media, et cetera.\nFrom what I can tell, not being drowned by depression is due to my medication, and the different kinds of therapy I\u0026rsquo;ve had since April last year.\nHelping to take care of my dad and helping my mom, cost a lot of time and energy. This left very little room for writing blogs. My energy-levels are still not back to (for me) normal, as my body and mind are still processing all that happened the last months.\nThis year will be for recertification of the Advanced Networking Specialty, and first steps into AI. On the personal level it will be about slowing down, enjoying my life, visiting Scotland again, continuing my coaching training, and making connections to people. Not too many, though. I\u0026rsquo;m still an introvert ;-)\n","date":"9 April 2026","externalUrl":null,"permalink":"/2026/04/09/personal-note-mental-health-update/","section":"Posts","summary":"A lot has happened the last 12 months. My dad’s health deteriorated more rapidly, my mom had some injuries which impeded on her mobility, and last January, my dad died as a result of COPD and pulmonary fibrosis.\n","title":"Personal note - mental health - update","type":"posts"},{"content":"","date":"9 April 2026","externalUrl":null,"permalink":"/tags/","section":"Tags","summary":"","title":"Tags","type":"tags"},{"content":"","date":"29 December 2025","externalUrl":null,"permalink":"/tags/aws-lambda/","section":"Tags","summary":"","title":"AWS Lambda","type":"tags"},{"content":"The code that accompanies this blogpost can be found here\nDeploying Lambda functions to AWS using Terraform can be quite a struggle, especially when deploying from multiple environments (which only happens in dev and test environments, am I right?).\nSome issues you can encounter are:\nLambda functions redeploying at every terraform apply Errors about missing archive files containing the Lambda function files Soft locks in the Terraform state file In this post I’ll be showing you a way to be able to consistently deploy Lambda functions, only when there are changes to the code, when deploying from multiple environments.\nHistory # There have been long time issues when deploying Lambda functions using Terraform. Some external links with examples of these issues and some attempts to tackle them:\nhttps://stackoverflow.com/q/52662244/19024815 I have an AWS Lambda deployed successfully with Terraform:\nresource \u0026#34;aws_lambda_function\u0026#34; \u0026#34;lambda\u0026#34; { filename = \u0026#34;dist/subscriber-lambda.zip\u0026#34; function_name = \u0026#34;test_get-code\u0026#34; role = \u0026lt;my_role\u0026gt; handler = \u0026#34;main.handler\u0026#34; timeout = 14 reserved_concurrent_executions = 50 memory_size = 128 runtime = \u0026#34;python3.6\u0026#34; tags = \u0026lt;my map of tags\u0026gt; source_code_hash = \u0026#34;${base64sha256(file(\u0026#34;../modules/lambda/lambda-code/main.py\u0026#34;))}\u0026#34; kms_key_arn = \u0026lt;my_kms_arn\u0026gt; vpc_config { subnet_ids = \u0026lt;my_list_of_private_subnets\u0026gt; security_group_ids = \u0026lt;my_list_of_security_groups\u0026gt; } environment { variables = { environment = \u0026#34;dev\u0026#34; } } } Now, when I run terraform plan command it says my lambda resource needs to be updated because the source_code_hash has changed, but I didn\u0026rsquo;t update lambda Python codebase (which is versioned in a folder of the same repo):\n~ module.app.module.lambda.aws_lambda_function.lambda last_modified: \u0026#34;2018-10-05T07:10:35.323+0000\u0026#34; =\u0026gt; \u0026lt;computed\u0026gt; source_code_hash: \u0026#34;jd6U44lfe4124vR0VtyGiz45HFzDHCH7+yTBjvr400s=\u0026#34; =\u0026gt; \u0026#34;JJIv/AQoPvpGIg01Ze/YRsteErqR0S6JsqKDNShz1w78\u0026#34; https://nulldog.com/trigger-terraform-lambda-code-uploads-a-guide Trigger updates with null_resource:\nIf you need more control or want to trigger updates based on other resources, use null_resource:\nresource \u0026#34;null_resource\u0026#34; \u0026#34;lambda_update\u0026#34; { triggers = { code_hash = filebase64sha256(\u0026#34;my-function.zip\u0026#34;) } provisioner \u0026#34;local-exec\u0026#34; { command = \u0026#34;echo \u0026#39;Code updated, triggering Lambda deployment...\u0026#39;\u0026#34; } } resource \u0026#34;aws_lambda_function\u0026#34; \u0026#34;example\u0026#34; { # ... other configurations depends_on = [null_resource.lambda_update] } This example triggers an update whenever the hash of \u0026ldquo;my-function.zip\u0026rdquo; changes.\nhttps://github.com/hashicorp/terraform-provider-aws/issues/17989 Hi All,\nWe are on Terraform 0.14.6 and experiencing the following issue. We are providing source_code_hash for the aws_lambda_layer_version in the plan terraform accepts it but writes totally different to the state file.\nIn the plan the source_code_hash is FyN0P9BvuTm023dkHFaWvAGmyD0rlhujGsPCTqaBGyw= however in the state file it becames c3forIEso3mJh74PY6HrhFK94GfJvQ4zG9rEIgBCBhw=.\nWhen I check the layer in AWS CLI the \u0026ldquo;CodeSha256\u0026rdquo;: c3forIEso3mJh74PY6HrhFK94GfJvQ4zG9rEIgBCBhw=,\nBased on this it does not matter what kind of source_code_hash I can not overwrite hash of filename.\nTF config.\nresource \u0026#34;aws_lambda_layer_version\u0026#34; \u0026#34;loader\u0026#34; { layer_name = \u0026#34;loader\u0026#34; compatible_runtimes = [\u0026#34;python3.8\u0026#34;] filename = \u0026#34;lambda_layer.zip\u0026#34; source_code_hash = filebase64sha256(\u0026#34;lambda_layer.zip\u0026#34;) } What you can see in all these examples, is that a hash is calculated to determine if the code has changed. That in itself isn’t an issue, but what is an issue, is that they all use a base64 encoded hash.\nHow to make your Lambda function deployment cross-environment friendly The issue with base64 encoding, is that the resulting hash for the same data, will differ across environments (operating systems, user settings).\nThe following post describes this issue:\nThe root cause of is this is difference in packaging on different machines and bad documentation. Well, and an asinine design choice on AWS part.\nsource_code_hash gets overwritten by AWS-provided data upon response. The documentation for source_code_hash (aka output_base64sha256, filebase64sha256) lies:\n(String) The base64-encoded SHA256 checksum of output archive file.\nWhy would you even want to base64-encode a hash? The purpose of base64 encoding is to do away with non-printable chars, which a hash doesn’t have.\nTurns out, what they actually do is compute sha256, then take the resulting text string and treat its characters as binary values, then base64 that: sha256sum lambda.zip | xxd -r -p | base64.\nThe problem is, recent zip versions store file permissions, and different umask values on different machines result in different permissions, which in turn produces different archives with different hashes.\nBut when you’re in a team where both Windows and macOS/Linux are being used, you have an additional challenge because the filesystems (and thus the filename of the archive-file) differ quite a lot.\nGetting it to work # After some tinkering, I came to the following solution.\nIn my example, I supply the Lambda function code as a directory, containing the required file(s). In code, I create an archive file from that directory, using the data-source archive_file.\nFirst, we create a random UUID, based on all the files (excluding ZIP-files) in the source directory (and child directories), and creating an MD5 hash for each of them.\n# Create a random UUID which is used to trigger a redeploy of the function. # The MD5 hash for each file (except ZIP-files) will be calculated and if any of those changes, # it will trigger a redeploy of the aws_lambda_function resource `lambda_function`. # We cannot rely on a base64 hash, because the seed for that is environment dependent. resource \u0026#34;random_uuid\u0026#34; \u0026#34;lambda_function\u0026#34; { keepers = { for filename in setunion( toset([for fn in fileset(\u0026#34;${path.root}/lambda_function/\u0026#34;, \u0026#34;**\u0026#34;) : fn if !endswith(fn, \u0026#34;.zip\u0026#34;)]), ) : filename =\u0026gt; filemd5(\u0026#34;${path.root}/lambda_function/${filename}\u0026#34;) } } I chose to use MD5 here, because we’re not using it for cryptographic purposes. You can just as easily select SHA256 or SHA512, which do require some additional resources when calculating them (which might very well be negligible).\nNext, we create a ZIP-file and send it to a different path than the source directory (which is excluded in .gitignore).\n# Create an archive file of the function directory data \u0026#34;archive_file\u0026#34; \u0026#34;lambda_function\u0026#34; { type = \u0026#34;zip\u0026#34; source_dir = \u0026#34;${path.root}/lambda_function\u0026#34; output_path = \u0026#34;${path.root}/lambda_output/${var.function_name}.zip\u0026#34; } When any of the source files changes, a new random UUID will be generated. To make sure this triggers a re-deployment of the Lambda function, we’ll set the random_uuid resource as a replacement trigger for the Lambda function resource.\nTo do this, we add a lifecycle-block to the aws_lambda_function resource, with a replace_triggered_by block, targeting the random_uuid.lambda resource. The archive file will be created every time a terraform plan or terraform apply is run. Since the location of the resulting archive file will be different for every user (remember, we’re talking about dev/test deployments here!), we also need to make sure that the filename of the archive file doesn’t trigger unnecessary redeployments, by adding an ignore_changes block to the lifecycle block, targeting the filename property of the aws_lambda_function resource.\n# Create the Lambda function resource \u0026#34;aws_lambda_function\u0026#34; \u0026#34;lambda_function\u0026#34; { function_name = var.function_name role = aws_iam_role.lambda_execution_role.arn handler = \u0026#34;${var.function_name}.${var.handler_name}\u0026#34; runtime = var.runtime timeout = var.timeout architectures = var.architectures # Use the filename of the archive file as input for the function filename = data.archive_file.lambda_function.output_path depends_on = [ aws_iam_role.lambda_execution_role ] lifecycle { replace_triggered_by = [ # Trigger a replace of the function when any of the function source files changes. random_uuid.lambda_function ] ignore_changes = [ # Ignore the source filename of the object itself, because that can change between # users/machines/operating systems. filename ] } } Once this has been applied, when you now run the same code across different environments, no unexpected/undesired re-deployments of the Lambda function will occur.\nThis same approach can be used for deploying Lambda Layers. The difference there is that there’s an intermediate in the form of an S3 object, which will be replaced when there’s a change in any of the source files. Which, in turn, triggers replacing the Lambda Layer with a new version.\nresource \u0026#34;random_uuid\u0026#34; \u0026#34;lambda_layer\u0026#34; { keepers = { for filename in setunion( toset([for fn in fileset(\u0026#34;${path.root}/lambda_layer/\u0026#34;, \u0026#34;**\u0026#34;) : fn if !endswith(fn, \u0026#34;.zip\u0026#34;)]), ) : filename =\u0026gt; filemd5(\u0026#34;${path.root}/lambda_layer/${filename}\u0026#34;) } } data \u0026#34;archive_file\u0026#34; \u0026#34;lambda_layer\u0026#34; { type = \u0026#34;zip\u0026#34; source_dir = \u0026#34;${path.root}/lambda_layer\u0026#34; output_path = \u0026#34;${path.root}/lambda_output/${var.layer_name}.zip\u0026#34; } resource \u0026#34;aws_s3_object\u0026#34; \u0026#34;this\u0026#34; { depends_on = [data.archive_file.lambda_layer] key = join(\u0026#34;/\u0026#34;, [for x in [var.s3_key, join(\u0026#34;.\u0026#34;, [var.layer_name, \u0026#34;zip\u0026#34;])] : x if x != null \u0026amp;\u0026amp; x != \u0026#34;\u0026#34;]) bucket = var.s3_bucket source = data.archive_file.lambda_layer.output_path checksum_algorithm = \u0026#34;SHA256\u0026#34; lifecycle { replace_triggered_by = [ random_uuid.lambda_layer ] ignore_changes = [ # Ignore the source of the object itself, because that can change between machines/operating systems source ] } } resource \u0026#34;aws_lambda_layer_version\u0026#34; \u0026#34;lambda_layer\u0026#34; { layer_name = var.layer_name compatible_runtimes = [var.runtime] source_code_hash = aws_s3_object.this.checksum_sha256 s3_bucket = aws_s3_object.this.bucket s3_key = aws_s3_object.this.key } When running all your IaC changes through a pipeline (as you should for at least production and staging/acceptance), this should not be an issue for you. But having your terraform plan/apply cluttered with false changes because of differences between contributor systems for your development and test-stages, should be in the past with this approach.\nThe Lambda module by Anton Babenko also uses base64 in its hash calculations, as well as the filename of the archive file. So if you run into (one of) the mentioned issues with that module, now you know why; I’ll be working on a PR to get the base64 part fixed for that module.\nConclusion # The goal of this post is to show you how to tackle (at least) two possible issues you might have with deploying Lambda functions and/or layers using Terraform. I hope to have given you some insight into the causes of these issues, and with that, to make an informed decision on how to tackle them.\n","date":"29 December 2025","externalUrl":null,"permalink":"/2025/12/29/consistently-deploying-lambda-functions-and-layers-using-terraform/","section":"Posts","summary":"The code that accompanies this blogpost can be found here\nDeploying Lambda functions to AWS using Terraform can be quite a struggle, especially when deploying from multiple environments (which only happens in dev and test environments, am I right?).\n","title":"Consistently deploying Lambda functions and layers using Terraform","type":"posts"},{"content":"","date":"29 December 2025","externalUrl":null,"permalink":"/tags/terraform/","section":"Tags","summary":"","title":"Terraform","type":"tags"},{"content":"Last year, one of my intentions was to write more blogs. As you might\u0026rsquo;ve seen, I didn\u0026rsquo;t manage to do so. I\u0026rsquo;ve been struggling with depression for a while, and last year I couldn\u0026rsquo;t find the energy to put much effort into writing. Some health-related \u0026lsquo;surprised\u0026rsquo; in the family didn\u0026rsquo;t help with that, either.\nDepression has been a part of me for a long time, and at times is more prevalent. It mostly seemed to be tied to the winter periods, but has slowly grown bigger over the years.\nIn 2018 I ended up in a burn-out situation. During my recovery, I was diagnosed with Autism Spectrum Syndrome (ASS). I got some therapy to help me understand what that meant for me. Depression played a role then as well. Since therapy started at the start of spring, and as my rhythm was different because of staying home, it was manageable and \u0026lsquo;in the background\u0026rsquo;.\nIn the second half of 2019 I slowly started to reintegrate at work again, which took me another year, and switching to a different team, before I was back. Not fully, though. I cut my hours back from 40 to 32 a week. During reintegration I experienced that working more hours a week got me into a vicious circle of self-neglect, self-destruction, and depression.\nI was able to manage things for a bit, but in the last 2 to 3 years, I became more aware of my unwanted pet, the black dog. I also started to be affected by some other mental issues, for which I sought advice. I was tested for ADHD, for which the diagnosis was negative. I then asked referral to a psychologist. That was November 2023. Last week I (finally) had my intake appointment, and things have been set in motion. And it\u0026rsquo;s going quite fast.\nI\u0026rsquo;m saying yes to whatever they can offer me, to, at the very least, determine if it works for me, or not. As a result, I\u0026rsquo;ll be putting a lot of time and effort in working on recovering. This entails (re-)learning about ASS, analysing my life to see where I can make better choices to get my energy back in balance, and in that way also be less affected by depression.\nIt\u0026rsquo;s not all black, in my life. I do get energy from being outdoors and being a volunteer assistant for bushcraft courses, as well as from a big, challenging and fun project I\u0026rsquo;m assigned to. I just hope that that energy will be something that can supercharge my battery, instead of it being the straw that keeps my head above water.\nSo this year will be a year for me to put more focus on my mental health. I still have to renew some of my certifications, as well as work, though ;-)\nAfter I had my intake appointment, part of the lyrics of a Dutch song came to mind:\nIk ga lopen tot de zon komt\nTot de zon me achterhaalt\nLopen tot de zon komt\nTot \u0026lsquo;ie straalt\u0026hellip;\nA rough translation:\nI\u0026rsquo;ll walk until the sun comes up\nUntil the sun catches up with me\nWalk until the sun comes\nUntil it shines\nThe song is about a different topic, but these (last) lines of the lyrics reflect what I hope my journey for this period in my life will be: moving out of the dark, towards the light, to be able to bask in the sun again.\n","date":"6 February 2025","externalUrl":null,"permalink":"/2025/02/06/personal-note-mental-health/","section":"Posts","summary":"Last year, one of my intentions was to write more blogs. As you might’ve seen, I didn’t manage to do so. I’ve been struggling with depression for a while, and last year I couldn’t find the energy to put much effort into writing. Some health-related ‘surprised’ in the family didn’t help with that, either.\n","title":"Personal note - mental health","type":"posts"},{"content":"","date":"29 December 2024","externalUrl":null,"permalink":"/tags/aws-iot-core/","section":"Tags","summary":"","title":"AWS IoT Core","type":"tags"},{"content":"The code that accompanies this blogpost can be found here\nI\u0026rsquo;ve been tinkering with AWS IoT Core this year, and wanted to put at least some of what I\u0026rsquo;ve found and done into a blog, so here it is.\nAWS IoT Core is the AWS Internet-of-Things service:\nAWS IoT provides the cloud services that connect your IoT devices to other devices and AWS cloud services. AWS IoT provides device software that can help you integrate your IoT devices into AWS IoT-based solutions. If your devices can connect to AWS IoT, AWS IoT can connect them to the cloud services that AWS provides.\nSetting up an IoT environment within AWS is pretty easy, but I want to put it in code, so I can easily reproduce the environment I set up, and also be able to easily remove the configuration. My tool of choice is (still) Terraform.\nWe\u0026rsquo;ll need to create the following resources:\nIAM role used for registering new things IoT policy for devices IoT thing group (optional) IoT thing type (optional) Pre-provisioning Lambda (optional) IoT fleet provisioning template (with optional pre-provisioning hook) IoT policy for provisioning Certificate for claim-based provisioning IoT event configurations (optional) I\u0026rsquo;ve included Terraform code in the accompanying GitHub repository.\nIAM role for registering new things # When provisioning, the role used by IoT requires permissions to register new things. A managed policy AWSIoTThingsRegistration exists for this purpose, which should be assigned to a (new) role.\n# Create the IoT provisioning IAM role resource \u0026#34;aws_iam_role\u0026#34; \u0026#34;iot_fleet_provisioning\u0026#34; { name = join(\u0026#34;-\u0026#34;, [var.name, \u0026#34;fleet-provisioning-role\u0026#34;]) assume_role_policy = jsonencode({ \u0026#34;Version\u0026#34; : \u0026#34;2012-10-17\u0026#34;, \u0026#34;Statement\u0026#34; : [ { \u0026#34;Effect\u0026#34; : \u0026#34;Allow\u0026#34;, \u0026#34;Principal\u0026#34; : { \u0026#34;Service\u0026#34; : \u0026#34;iot.amazonaws.com\u0026#34; }, \u0026#34;Action\u0026#34; : \u0026#34;sts:AssumeRole\u0026#34; } ] }) } # Attach the managed role for registering things to the provisioning role resource \u0026#34;aws_iam_role_policy_attachment\u0026#34; \u0026#34;iot_fleet_provisioning\u0026#34; { role = aws_iam_role.iot_fleet_provisioning.name policy_arn = \u0026#34;arn:aws:iam::aws:policy/service-role/AWSIoTThingsRegistration\u0026#34; } # Ensure that these (managed) policies are the only ones attached to the provisioning role on every apply resource \u0026#34;aws_iam_role_policy_attachments_exclusive\u0026#34; \u0026#34;iot_fleet_provisioning\u0026#34; { role_name = aws_iam_role.iot_fleet_provisioning.name policy_arns = [ \u0026#34;arn:aws:iam::aws:policy/service-role/AWSIoTThingsRegistration\u0026#34;, ] } IoT policy for devices # What actions are allowed for a thing, will be defined in an IoT policy. A thing authenticates with IoT Core using a device-specific certificate. During provisioning, the policy will be assigned to the device-specific certificate, as defined in the fleet provisioning template.\nBelow policy allows\n# Create a device policy resource \u0026#34;aws_iot_policy\u0026#34; \u0026#34;iot_device\u0026#34; { name = join(\u0026#34;-\u0026#34;, [var.name, \u0026#34;device-policy\u0026#34;]) policy = jsonencode({ \u0026#34;Version\u0026#34; : \u0026#34;2012-10-17\u0026#34;, \u0026#34;Statement\u0026#34; : [ { \u0026#34;Effect\u0026#34; : \u0026#34;Allow\u0026#34;, \u0026#34;Action\u0026#34; : [ \u0026#34;iot:Publish\u0026#34;, \u0026#34;iot:Receive\u0026#34; ], \u0026#34;Resource\u0026#34; : [ \u0026#34;arn:aws:iot:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:topic/$${iot:Connection.Thing.ThingName}/*\u0026#34;, ] }, { \u0026#34;Effect\u0026#34; : \u0026#34;Allow\u0026#34;, \u0026#34;Action\u0026#34; : \u0026#34;iot:Subscribe\u0026#34;, \u0026#34;Resource\u0026#34; : [ \u0026#34;arn:aws:iot:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:topicfilter/$aws/things/$${iot:Connection.Thing.ThingName}/shadow/*\u0026#34;, ] }, { \u0026#34;Condition\u0026#34; : { \u0026#34;Bool\u0026#34; : { \u0026#34;iot:Connection.Thing.IsAttached\u0026#34; : [ \u0026#34;true\u0026#34; ] } }, \u0026#34;Effect\u0026#34; : \u0026#34;Allow\u0026#34;, \u0026#34;Action\u0026#34; : \u0026#34;iot:Connect\u0026#34;, \u0026#34;Resource\u0026#34; : \u0026#34;*\u0026#34; } ] }) } IoT thing group (optional) # Thing groups are optional, and can be used to group things together. There are static and dynamic thing groups. There\u0026rsquo;s a limit of 100 dynamic groups per account, so if you\u0026rsquo;ve got a large environment with possibly a lot of groups, think ahead on whether or not you can use dynamic groups.\nDynamic thing groups are created from specific search queries in the registry. Search query parameters such as device connectivity, device shadow creation, and AWS IoT Device Defender violations data support this. Dynamic thing groups require fleet indexing enabled to index, search, and aggregate your devices\u0026rsquo; data.\nStatic thing groups allow you to manage several things at once by categorizing them into groups. Static thing groups contain a group of things that are managed by using the console, CLI, or the API.\nIn this example we\u0026rsquo;re using a static group, which the new thing will be assigned to by the fleet provisioning template.\n# Create a Thing group resource \u0026#34;aws_iot_thing_group\u0026#34; \u0026#34;provisioning\u0026#34; { name = \u0026#34;Provisioning\u0026#34; } IoT thing type (optional) # Thing types allow you to store description and configuration information that is common to all things associated with the same thing type.\nAlthough thing types are optional, their use makes it easier to discover things.\nThings with a thing type can have up to 50 attributes. Things without a thing type can have up to three attributes. A thing can be associated with only one thing type. There is no limit on the number of thing types you can create in your account. In this example we\u0026rsquo;re creating a single thing type, which is assigned to the thing by the fleet provisioning template.\n# Create a Thing type # The delete process of these is that they\u0026#39;ll be deprecated first, # and 5 minutes later they can be deleted. resource \u0026#34;aws_iot_thing_type\u0026#34; \u0026#34;example\u0026#34; { name = \u0026#34;Example\u0026#34; properties { description = \u0026#34;Example\u0026#34; searchable_attributes = [ # There\u0026#39;s a maximum of 3 searchable attributes per Thing Type \u0026#34;environment\u0026#34;, \u0026#34;license\u0026#34;, ] } } Pre-provisioning Lambda (optional) # AWS recommends using pre-provisioning hook functions when creating provisioning templates to allow more control of which and how many devices your account onboards. Pre-provisioning hooks are Lambda functions that validate parameters passed from the device before allowing the device to be provisioned. This Lambda function must exist in your account before you provision a device because it\u0026rsquo;s called every time a device sends a request through RegisterThing.\nIn this example we\u0026rsquo;re deploying a simple Lambda-function used for pre-provisioning. No logic is built into the Lambda, but it shows that you can have a gatekeeper present in your provisioning process. You could, for example, check if the license-number provided by the thing for registering is valid, if the IP-address of the thing is as expected, if you\u0026rsquo;ve reached your maximum number of things you want to register, et cetera.\nimport json def pre_provisioning_hook(event, context): print(event) # You can put code here to check if a device trying to connect # should be allowed or not, like checking if any of the provided # attributes are valid. # This function has to be able to respond within 5 seconds, # otherwise the provisioning request fails. # Reference: https://docs.aws.amazon.com/iot/latest/developerguide/pre-provisioning-hook.html # If you want to allow the device to connect to IoT Core, return this: # \u0026#39;allowProvisioning\u0026#39;: True # If you want to disallow the device to connect to IoT Core, return this: # \u0026#39;allowProvisioning\u0026#39;: False return { \u0026#39;allowProvisioning\u0026#39;: True } IoT fleet provisioning template (with optional pre-provisioning hook) # The fleet provisioning template is what ties together all the resources we created previously.\nThis template defines the following:\nThe parameters it expects to receive from a thing that\u0026rsquo;s submitting itself for registration, to be used in the template The resource required for the thing to communicate with IoT; a thing, a certificate and one or more policies. This is where we assign the device policy to the thing, through the device-specific certificate which will be created for the thing. It will assign the thing to the initial thing group, and assign a thing type. By using multiple fleet provisioning templates, with different provisioning certificates, you can easily register different types of devices in your IoT environment, and assign device-specific attributes to them.\n# Create the fleet provisioning template resource \u0026#34;aws_iot_provisioning_template\u0026#34; \u0026#34;fleet\u0026#34; { name = join(\u0026#34;-\u0026#34;, [var.name, \u0026#34;fleet-provisioning-tpl\u0026#34;]) description = \u0026#34;Fleet provisioning template for ${var.name}\u0026#34; provisioning_role_arn = aws_iam_role.iot_fleet_provisioning.arn enabled = true template_body = jsonencode({ \u0026#34;DeviceConfiguration\u0026#34; : {}, \u0026#34;Parameters\u0026#34; : { \u0026#34;License\u0026#34; : { \u0026#34;Type\u0026#34; : \u0026#34;String\u0026#34; }, \u0026#34;AWS::IoT::Certificate::Id\u0026#34; : { \u0026#34;Type\u0026#34; : \u0026#34;String\u0026#34; } }, \u0026#34;Resources\u0026#34; : { \u0026#34;policy\u0026#34; : { \u0026#34;Type\u0026#34; : \u0026#34;AWS::IoT::Policy\u0026#34;, \u0026#34;Properties\u0026#34; : { \u0026#34;PolicyName\u0026#34; : aws_iot_policy.iot_device.name } }, \u0026#34;certificate\u0026#34; : { \u0026#34;Type\u0026#34; : \u0026#34;AWS::IoT::Certificate\u0026#34;, \u0026#34;Properties\u0026#34; : { \u0026#34;CertificateId\u0026#34; : { \u0026#34;Ref\u0026#34; : \u0026#34;AWS::IoT::Certificate::Id\u0026#34; }, \u0026#34;Status\u0026#34; : \u0026#34;Active\u0026#34; } }, \u0026#34;thing\u0026#34; : { \u0026#34;Type\u0026#34; : \u0026#34;AWS::IoT::Thing\u0026#34;, \u0026#34;OverrideSettings\u0026#34; : { \u0026#34;AttributePayload\u0026#34; : \u0026#34;MERGE\u0026#34;, \u0026#34;ThingGroups\u0026#34; : \u0026#34;REPLACE\u0026#34;, \u0026#34;ThingTypeName\u0026#34; : \u0026#34;REPLACE\u0026#34; }, \u0026#34;Properties\u0026#34; : { \u0026#34;AttributePayload\u0026#34; : { \u0026#34;license\u0026#34; : { \u0026#34;Ref\u0026#34; : \u0026#34;License\u0026#34; }, }, \u0026#34;ThingGroups\u0026#34; : [ aws_iot_thing_group.provisioning.name ], \u0026#34;ThingTypeName\u0026#34; : aws_iot_thing_type.example.name, \u0026#34;ThingName\u0026#34; : { \u0026#34;Fn::Join\u0026#34; : [ \u0026#34;-\u0026#34;, [ \u0026#34;iot\u0026#34;, { \u0026#34;Ref\u0026#34; : \u0026#34;License\u0026#34; } ] ] } } } } }) pre_provisioning_hook { target_arn = aws_lambda_function.iot_preprovisioning.arn payload_version = \u0026#34;2020-04-01\u0026#34; } } IoT policy for provisioning # The environment we\u0026rsquo;re setting up, uses the \u0026lsquo;provisioning with claim\u0026rsquo; provisioning method. This means we don\u0026rsquo;t have to create device certificates in advance, but a new device will register itself using a generic provisioning certificate.\nBecause this certificate will be \u0026lsquo;out in the wild\u0026rsquo;, we want to restrict the permissions it provides as much as possible. This means the certificate should only be allowed to be used to register a new thing, and create a device-specific certificate for that thing. This is also why we want to add the pre-provisioning hook as a gatekeeper.\nBelow policy allows the thing to connect to IoT, subscribe to, publish too and receive from MQTT topics related to certificate creation and provisioning, specific to a provisioning template.\n# Create the claims provisioning certificate policy resource \u0026#34;aws_iot_policy\u0026#34; \u0026#34;provisioning\u0026#34; { name = join(\u0026#34;-\u0026#34;, [var.name, \u0026#34;claim-certificate-policy\u0026#34;]) policy = jsonencode({ \u0026#34;Version\u0026#34; : \u0026#34;2012-10-17\u0026#34;, \u0026#34;Statement\u0026#34; : [ { \u0026#34;Effect\u0026#34; : \u0026#34;Allow\u0026#34;, \u0026#34;Action\u0026#34; : \u0026#34;iot:Connect\u0026#34;, \u0026#34;Resource\u0026#34; : \u0026#34;*\u0026#34; }, { \u0026#34;Effect\u0026#34; : \u0026#34;Allow\u0026#34;, \u0026#34;Action\u0026#34; : [ \u0026#34;iot:Publish\u0026#34;, \u0026#34;iot:Receive\u0026#34; ], \u0026#34;Resource\u0026#34; : [ \u0026#34;arn:aws:iot:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:topic/$aws/certificates/create/*\u0026#34;, \u0026#34;arn:aws:iot:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:topic/$aws/provisioning-templates/${aws_iot_provisioning_template.fleet.name}/provision/*\u0026#34; ] }, { \u0026#34;Effect\u0026#34; : \u0026#34;Allow\u0026#34;, \u0026#34;Action\u0026#34; : \u0026#34;iot:Subscribe\u0026#34;, \u0026#34;Resource\u0026#34; : [ \u0026#34;arn:aws:iot:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:topicfilter/$aws/certificates/create/*\u0026#34;, \u0026#34;arn:aws:iot:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:topicfilter/$aws/provisioning-templates/${aws_iot_provisioning_template.fleet.name}/provision/*\u0026#34; ] } ] }) } Certificate for claim-based provisioning # As the provisioning certificate, we\u0026rsquo;re going to use a self-signed certificate. Reasons for using a self-signed certificate:\nThe ability to set how long the certificate will be valid Not having to set up a Certificate Authority An AWS IoT-generated certificate doesn\u0026rsquo;t have the proper allowed uses to connect to MQTT For this example we\u0026rsquo;re setting the lifetime of the certificate to 365 days. The lifetime that\u0026rsquo;s right for your environment will depend on things like how often you\u0026rsquo;ll update/deploy the application that includes the provisioning template, and how easy it is to update the certificate in that application.\nThe IoT policy for provisioning will be assigned to this certificate, to make sure it\u0026rsquo;s not going to be used for any actions other than registering a new thing.\n# Create a self-signed provisioning certificate resource \u0026#34;tls_private_key\u0026#34; \u0026#34;provisioning\u0026#34; { algorithm = \u0026#34;RSA\u0026#34; rsa_bits = 2048 } resource \u0026#34;tls_self_signed_cert\u0026#34; \u0026#34;provisioning\u0026#34; { private_key_pem = tls_private_key.provisioning.private_key_pem subject { common_name = \u0026#34;IoT Provisioning\u0026#34; } validity_period_hours = 8760 # 365 days allowed_uses = [ \u0026#34;key_encipherment\u0026#34;, \u0026#34;digital_signature\u0026#34;, \u0026#34;server_auth\u0026#34;, ] } # Add the provisioning certificate and attach the provisioning policy resource \u0026#34;aws_iot_certificate\u0026#34; \u0026#34;iot_fleet_provisioning\u0026#34; { certificate_pem = tls_self_signed_cert.provisioning.cert_pem active = true } resource \u0026#34;aws_iot_policy_attachment\u0026#34; \u0026#34;iot_fleet_provisioning_certificate\u0026#34; { policy = aws_iot_policy.provisioning.name target = aws_iot_certificate.iot_fleet_provisioning.arn } IoT event configurations (optional) # If you want to be able to act on IoT events, those will need to be enabled.\nEnabling these, facilitates these events being published to specific MQTT topics. These can be used in IoT rules, to trigger actions when specific events happen. They can also be used by things, as long as the device policy grants permissions to subscribe to and receive from those topics.\nThis example enables events related to thing creation, updates, and deletion.\n# Manage events that will publish messages to MQTT topics. # Reference: https://docs.aws.amazon.com/iot/latest/developerguide/iot-events.html#iot-events-enable resource \u0026#34;aws_iot_event_configurations\u0026#34; \u0026#34;this\u0026#34; { event_configurations = { \u0026#34;THING\u0026#34; = true, \u0026#34;THING_GROUP\u0026#34; = false, \u0026#34;THING_TYPE\u0026#34; = false, \u0026#34;THING_GROUP_MEMBERSHIP\u0026#34; = false, \u0026#34;THING_GROUP_HIERARCHY\u0026#34; = false, \u0026#34;THING_TYPE_ASSOCIATION\u0026#34; = false, \u0026#34;JOB\u0026#34; = false, \u0026#34;JOB_EXECUTION\u0026#34; = false, \u0026#34;POLICY\u0026#34; = false, \u0026#34;CERTIFICATE\u0026#34; = false, \u0026#34;CA_CERTIFICATE\u0026#34; = false, } } IoT logging to CloudWatch (not included) # By default, AWS IoT Core doesn\u0026rsquo;t log to CloudWatch. You can enable this in the console under Settings, or using the Terraform resource aws_iot_logging_options. This will incur extra costs, so do keep an eye on that.\nIaC caveats # Lack of resource support # Not all resources can be created using Terraform. Support for jobs and jobs templates is missing, for example, which are resources that really help to create a workflow for provisioning and staging your things.\nThese resources can be created using the AWS SDK, so a workaround for this shortcoming is to create a Lambda that performs the desired action in a dynamic environment, or use the SDK in a script, when your environment is more static.\nA way to leverage a Lambda function when deploying your environment using Terraform, is the resource aws_lambda_invocation. This way, if you set the triggers correctly, the Lambda-function will be invoked when any of the triggers changes.\nAnother solution can be to use a step function to orchestrate the provisioning of a new thing, but might be a bit of overkill, depending on the size of your environment. Starting out with one or more (simple) Lambda functions and later on refactoring this to a step function is always an option.\nDestroying your environment # When destroying the environment using Terraform, any device certificate needs to be deactivated, and detached from any policy (and thing). Otherwise the policies cannot be removed.\nSample client # Now that we\u0026rsquo;ve set up the IoT environment, it\u0026rsquo;s time to test it. I\u0026rsquo;ve included a Python sample client in the accompanying GitHub repository, which registers itself with IoT Core, and writes the device-specific certificates to disk.\n❯ python3 ./iotservice.py Connecting to akgbiozgh01fa-ats.iot.eu-west-1.amazonaws.com with client ID \u0026#39;iot-123\u0026#39;... Lifecycle Connection Success Connected! Subscribing to CreateKeysAndCertificate Accepted topic... Subscribing to CreateKeysAndCertificate Rejected topic... Subscribing to RegisterThing Accepted topic... Subscribing to RegisterThing Rejected topic... Publishing to CreateKeysAndCertificate... Waiting... CreateKeysAndCertificateResponse: null Published CreateKeysAndCertificate request.. Received a new message awsiot.iotidentity.CreateKeysAndCertificateResponse(certificate_id=\u0026#39;\u0026lt;CERTIFICATE_ID\u0026gt;\u0026#39;, certificate_ownership_token=\u0026#39;\u0026lt;CERTIFICATE_OWNERSHIP_TOKEN\u0026gt;\u0026#39;, certificate_pem=\u0026#39;\u0026lt;CERTIFICATE_PEM\u0026gt;\u0026#39;, private_key=\u0026#39;\u0026lt;PRIVATE_KEY\u0026gt;\u0026#39;) Publishing to RegisterThing topic... Waiting... RegisterThingResponse: null Published RegisterThing request.. Received a new message awsiot.iotidentity.RegisterThingResponse(device_configuration={}, thing_name=\u0026#39;iot-123\u0026#39;) Exiting Sample: success Stop the Client... No Client to stop Thing name: iot-123 This example is based on examples provided by AWS.\nDevice registration caveats # The device-specific certificates are written to disk in the function registerthing_execution_accepted in fleetprovisioning_mqtt5.py. IoT Core creates the device certificates before pre-provisioning has finished. When writing the certificates to disk, while the device is rejected by pre-provisioning, any later attempts to connect can fail, because there are already device-specific certificates present on the device. That would mean the certificates on the device need to be removed, before a new attempt can be made.\nAlso, because the certificate is created before the device is actually accepted, there will be certificates listed in IoT Core with the status Pending activation.\nConclusion # I\u0026rsquo;ve had fun this year figuring out things like this, and hope I\u0026rsquo;ve been able to provide you with enough information to set up your own IoT Core environment and play around with it.\nThink ahead of the challenges you think you\u0026rsquo;ll be facing, and be agile. Start small, and prepare for expanding to a larger scale. And as always, variables and requirements can (and probably will) change. Knowing what your options are, what the pros and cons are of those options will greatly help in picking the solutions you need both short, and long term.\n","date":"29 December 2024","externalUrl":null,"permalink":"/2024/12/29/setting-up-aws-iot-core-using-terraform/","section":"Posts","summary":"The code that accompanies this blogpost can be found here\nI’ve been tinkering with AWS IoT Core this year, and wanted to put at least some of what I’ve found and done into a blog, so here it is.\n","title":"Setting up AWS IoT Core using Terraform","type":"posts"},{"content":"","date":"7 December 2024","externalUrl":null,"permalink":"/tags/aws-elastic-container-service/","section":"Tags","summary":"","title":"AWS Elastic Container Service","type":"tags"},{"content":"","date":"7 December 2024","externalUrl":null,"permalink":"/tags/aws-image-builder/","section":"Tags","summary":"","title":"AWS Image Builder","type":"tags"},{"content":"","date":"7 December 2024","externalUrl":null,"permalink":"/tags/aws-simple-notification-service/","section":"Tags","summary":"","title":"AWS Simple Notification Service","type":"tags"},{"content":"This is a short follow-up on my previous post.\nIn my previous post about creating an AMI with Image Builder, I exported that AMI to a different image format (vmdk).\nThere\u0026rsquo;s two things I ran into with that solution:\nYou can only export to a single other image format There is no apparent correlation between the exported image and the original AMI To work around those two items, I created an ECS task definition, which uses a container with QEMU installed, as well as Python and Boto3. The entrypoint for the container is a Python script to handle the image conversion.\nI set up the Image Builder pipeline without the export to S3, and have it send a notification to SNS.\nThe notification triggers a Lambda function (for which I used Python), which checks the status (event['Records'][0]['Sns']['Message']['state']['status']) of the pipeline run. This will be either FAILED or AVAILABLE.\nThe SNS message also contains the AMI ID (event['Records'][0]['Sns']['Message']['outputResources']['amis'][0]['image']), pipeline version (event['Records'][0]['Sns']['Message']['version']), build version (event['Records'][0]['Sns']['Message']['buildVersion']) and the name of the Image Builder pipeline (event['Records'][0]['Sns']['Message']['name']).\nThese are used by the Lambda function, to run an ECS task, using the pre-defined task definition with these values as environment variables for the ECS task.\nThe ECS task uses the AMI ID to start an export, which takes time. How much, depends on the AMI you\u0026rsquo;re exporting. Since the export is initiated using Boto3, we get the export image task ID (ExportImageTaskId) in the response; this gives us the correlation we miss when we do the export using the distribution configuration of the pipeline. Once the export is done, you can pick up the exported image, and do additional conversions using the qemu-img command.\nYou can use the pipeline name, version and build version to name the images, so you can correlate them to the actual pipeline run that created the AMI you\u0026rsquo;ve created your export(s) from.\nFor exporting the images, the filesystem that\u0026rsquo;s assigned to the Lambda function is used. In the task definition you can override the storage size using the EphemeralStorage setting.\nWhen converting to multiple image formats, you might want to consider using multiprocessing. That way you can upload an image as soon as it\u0026rsquo;s conversion is done, while also being able to start conversion for the next image format.\nOnce all the conversions are done, you could, once again, send out an SNS message to inform you of the status of the conversion process.\nNo code this time, but I did want to at least describe how you can run your custom process once the AMI is created.\n","date":"7 December 2024","externalUrl":null,"permalink":"/2024/12/07/exporting-an-ami-to-multiple-formats/","section":"Posts","summary":"This is a short follow-up on my previous post.\nIn my previous post about creating an AMI with Image Builder, I exported that AMI to a different image format (vmdk).\nThere’s two things I ran into with that solution:\n","title":"Exporting an AMI to multiple formats","type":"posts"},{"content":"The code that accompanies this blogpost can be found here\nUpdate 2024-12-07: In the examples I use Amazon Linux 2023, which cannot be exported to other formats. I\u0026rsquo;ve changed the code in the repository to use an Ubuntu 24.04 base image, which can be exported. I\u0026rsquo;ve been working with AWS Image Builder a lot more over the last couple of months, while replacing a Packer setup that was run on a Windows laptop, with Image Builder.\nFrom the AWS Image Builder landing page:\nEC2 Image Builder simplifies the building, testing, and deployment of Virtual Machine and container images for use on AWS or on-premises.\nKeeping Virtual Machine and container images up-to-date can be time consuming, resource intensive, and error-prone. Currently, customers either manually update and snapshot VMs or have teams that build automation scripts to maintain images.\nImage Builder significantly reduces the effort of keeping images up-to-date and secure by providing a simple graphical interface, built-in automation, and AWS-provided security settings. With Image Builder, there are no manual steps for updating an image nor do you have to build your own automation pipeline.\nImage Builder is offered at no cost, other than the cost of the underlying AWS resources used to create, store, and share the images.\nThere are some caveats when using Image Builder:\nEBS encryption by default should be off. An encrypted volume cannot be exported to an alternative image format. The S3 bucket the exported image will be stored in, should use the AWS managed KMS key for S3 (SSE-S3) for encryption. AWS encourages you to use IMDSv2 when running EC2 instances. This requires an adjustments to any scripts querying the instance metadata, as well as an adjustment to the maximum number of hops for an HTTP put request. More information on these caveats can be found later in this post.\nCreating an Image Builder pipeline # To create an Image Builder pipeline, the following resources are needed:\nIAM roles with permissions for building the Amazon Machine Image (AMI), lifecycle management of the created AMIs, and for exporting the AMI to an additional image format An S3 bucket to export the additional image format to Any custom components for building your custom AMI An image recipe An infrastructure configuration A distribution configuration The Image Builder pipeline (Optional) an SNS topic In the GitHub repository I\u0026rsquo;ve linked, the code for the IAM-roles can be found in iam.tf, the code for the S3 bucket can be found in s3.tf, the code for SNS can be found in sns.tf, and the code for the remaining resources can be found in main.tf.\nMake sure you\u0026rsquo;re using at least version 5.74.0 of the Terraform AWS provider, to be able to enjoy these enhancements:\nIn version 5.74.0 support was added to the aws_imagebuilder_distribution_configuration resource for exporting the AMI to S3. In version 5.59.0 support was added to the aws_imagebuilder_image_pipeline resource to set the workflow of the pipeline. Custom components # A component can have multiple steps, in any of the two phases build ortest. It should have at least one step, and can contain steps for both build as well as test.\nThe first phase that is run, is the build phase. This is where the initial image if built. After the image has been created, a new EC2 instance (or container) will be started using that image, to run the test steps of the used components.\nIn the example, I\u0026rsquo;m using a simple component, which sets the timezone of the AMI to Europe/Amsterdam during the build phase.\nresource \u0026#34;aws_imagebuilder_component\u0026#34; \u0026#34;set_timezone\u0026#34; { name = join(\u0026#34;-\u0026#34;, [var.name, \u0026#34;set-timezone-linux\u0026#34;]) description = \u0026#34;Sets the timezone to Europe/Amsterdam\u0026#34; platform = \u0026#34;Linux\u0026#34; version = \u0026#34;1.0.0\u0026#34; skip_destroy = false # Setting this to true retains any previous versions data = yamlencode({ schemaVersion = 1.0 phases = [{ name = \u0026#34;build\u0026#34; steps = [ { name = \u0026#34;SetTimezone\u0026#34; action = \u0026#34;ExecuteBash\u0026#34; onFailure = \u0026#34;Abort\u0026#34; inputs = { commands = [ \u0026#34;timedatectl set-timezone Europe/Amsterdam\u0026#34; ] } } ] }] }) } Image recipe # The image recipe brings together all the \u0026lsquo;ingredients\u0026rsquo; that make the image.\nThe recipe is where you define the source image (parent_image) you\u0026rsquo;re building on, making overrides to settings of the source, as well as adding your own or AWS managed components, which are executed in the order they\u0026rsquo;re listed in the recipe (per phase).\nYou can also have the SSM agent removed after building the image. This is useful to do if the image will be used outside of AWS, where the SSM agent has no use.\nresource \u0026#34;aws_imagebuilder_image_recipe\u0026#34; \u0026#34;this\u0026#34; { # Currently the service only supports x86-based images for import or export. name = join(\u0026#34;-\u0026#34;, [var.name, \u0026#34;image-recipe\u0026#34;]) parent_image = \u0026#34;arn:aws:imagebuilder:eu-west-1:aws:image/amazon-linux-2023-ecs-optimized-x86/x.x.x\u0026#34; version = \u0026#34;1.0.0\u0026#34; block_device_mapping { # The device name is the same device name as the root volume of the selected AMI, # which means we\u0026#39;re overriding (some of) the root disk configuration in the AMI. # In this case we\u0026#39;re increasing the size of the disk from 20 GB to 40 GB. device_name = \u0026#34;/dev/xvda\u0026#34; no_device = false ebs { delete_on_termination = true volume_size = 40 volume_type = \u0026#34;gp3\u0026#34; encrypted = false iops = 3000 throughput = 125 } } # Add the components to the recipe. # Recipes require a minimum of one build component, and can have a maximum of 20 build and test components in any combination. # Components are executed in the order they are listed here. component { # Here we\u0026#39;re adding an AWS managed component to install the AWS CLI component_arn = \u0026#34;arn:aws:imagebuilder:${data.aws_region.current.name}:aws:component/aws-cli-version-2-linux/x.x.x\u0026#34; } component { # Here we\u0026#39;re adding our custom component component_arn = aws_imagebuilder_component.set_timezone.arn } systems_manager_agent { # Set this to false to keep the SSM agent installed after building the image. uninstall_after_build = true } lifecycle { # Adding resources to the replace_triggered_by, ensures that replacing a resource doesn\u0026#39;t fail because of dependencies. # Instead, this resource will be replaced as well. replace_triggered_by = [ aws_imagebuilder_component.set_timezone ] } } Infrastructure configuration # The infrastructure configuration defines what instance type(s) can be used to build and test the image, which subnet the build/test instances should use, as well as which security group(s) should be attached to the instance. The instance profile to use is also defined here, as well as the SNS topic to send messages to upon either success or failure of the pipeline run.\nIf no subnet ID and security group IDs are provided, a subnet from the default VPC will be used, with the default security group. When providing a subnet ID, one or more security group IDs must also be provided.\nIf you run into issues during the build phase, you can set terminate_instance_on_failure to false. This means the build-instance will not be terminated, and can be used to investigate the issue.\nIn this example, IMDSv2 is used (http_tokens = required). Also see here for more information about the http_put_response_hop_limit.\nresource \u0026#34;aws_imagebuilder_infrastructure_configuration\u0026#34; \u0026#34;this\u0026#34; { name = join(\u0026#34;-\u0026#34;, [var.name, \u0026#34;infrastructure-config\u0026#34;]) description = \u0026#34;Infrastructure Configuration for ${var.name}.\u0026#34; instance_profile_name = aws_iam_instance_profile.imagebuilder_build.name instance_types = var.instance_types sns_topic_arn = aws_sns_topic.this.arn # If you want to keep the instance when an error occurs, so you can debug the issue, set this to false terminate_instance_on_failure = true # When not providing a subnet id and security group id(s), # Image Builder uses a subnet in the default VPC with the default security group. security_group_ids = var.security_group_ids subnet_id = var.subnet_id instance_metadata_options { http_tokens = \u0026#34;required\u0026#34; http_put_response_hop_limit = 1 # Increase this to 3 when building a container image } tags = { ImageType = \u0026#34;CustomisedAmazonLinux2023Image\u0026#34; } } Distribution configuration # The distribution configuration tells Image Builder how to name the output AMI, and how to distribute the output AMI to different accounts, regions, organisations, and export the AMI to an alternative image format (VHD, VMDK or RAW)\nresource \u0026#34;aws_imagebuilder_distribution_configuration\u0026#34; \u0026#34;this\u0026#34; { name = join(\u0026#34;-\u0026#34;, [var.name, \u0026#34;distribution-config\u0026#34;]) description = \u0026#34;Distribution Configuration for ${var.name}.\u0026#34; distribution { region = data.aws_region.current.name ami_distribution_configuration { name = join(\u0026#34;-\u0026#34;, [var.name, \u0026#34;{{ imagebuilder:buildDate }}-{{ imagebuilder:buildVersion }}\u0026#34;]) kms_key_id = null ami_tags = { ImageType = \u0026#34;CustomisedAmazonLinux2023Image\u0026#34; } } s3_export_configuration { role_name = aws_iam_role.vmexport.name disk_image_format = upper(var.image_export_format) s3_bucket = aws_s3_bucket.this.id } } } Image Builder pipeline # The Image Builder pipeline is what ties all the previous resources together. This orchestrates building the image, and additionally trigger scanning of the output AMI for security issues. Amazon Inspector should be enabled in the account to be able to scan the image.\nThe pipeline also defines the workflow to use. By default, a workflow that runs both the build and the test phases is used. In the example, no test components are used, so we\u0026rsquo;re shaving some time off of the pipeline runtime, by selecting an AWS-managed workflow that only runs the build phase. When changing the default workflow, an execution role must also be provided.\nThe pipeline can also be scheduled to run at certain intervals using cron expressions. The pipeline can also be triggered using EventBridge rules, which requires additional resources to set up, which are not included in this sample.\nWhen no schedule is provided, the pipeline can only be run manually, or when targeted by an EventBridge rule.\nresource \u0026#34;aws_imagebuilder_image_pipeline\u0026#34; \u0026#34;this\u0026#34; { name = join(\u0026#34;-\u0026#34;, [var.name, \u0026#34;image-pipeline\u0026#34;]) description = \u0026#34;Pipeline to create the custom image for ${var.name}\u0026#34; image_recipe_arn = aws_imagebuilder_image_recipe.this.arn infrastructure_configuration_arn = aws_imagebuilder_infrastructure_configuration.this.arn distribution_configuration_arn = aws_imagebuilder_distribution_configuration.this.arn image_scanning_configuration { # Amazon Inspector needs to be enabled for the account when setting this to true image_scanning_enabled = false } image_tests_configuration { image_tests_enabled = true timeout_minutes = 720 } # When changing the workflow from default, an execution role must also be provided execution_role = \u0026#34;arn:aws:iam::${data.aws_caller_identity.account.account_id}:role/aws-service-role/imagebuilder.amazonaws.com/AWSServiceRoleForImageBuilder\u0026#34; workflow { # We\u0026#39;re setting an AWS managed workflow, that only executes Build-steps of the component. No testing or validation is done. workflow_arn = \u0026#34;arn:aws:imagebuilder:${data.aws_region.current.name}:aws:workflow/build/build-image/x.x.x\u0026#34; } # Here you can set one or more schedules, to automate image building. dynamic \u0026#34;schedule\u0026#34; { for_each = var.schedule_expression != null ? [1] : [] content { schedule_expression = var.schedule_expression } } lifecycle { # Adding resources to the replace_triggered_by, ensures that replacing a resource doesn\u0026#39;t fail because of dependencies. # Instead, this resource will be replaced as well. replace_triggered_by = [ aws_imagebuilder_image_recipe.this ] } } More info on the caveats of using Image Builder # EBS Encryption by default # To check if EBS encryption by default is enabled, we can use the following AWS CLI command:\n$ aws ec2 get-ebs-encryption-by-default { \u0026#34;EbsEncryptionByDefault\u0026#34;: false } If it\u0026rsquo;s true, check for Service Control Policies (SCPs) and/or other tooling used for managing the organisation/accounts. If this feature has been enabled through an SCP or other automated way, disable it for the account you\u0026rsquo;ll be running Image Builder in.\nTo manually disable EBS encryption by default, the following AWS CLI command can be used:\n$ aws ec2 disable-ebs-encryption-by-default { \u0026#34;EbsEncryptionByDefault\u0026#34;: false } If EBS encryption by default has been set using an SCP or other tooling, manually disabling will not be permanent, or might not even take effect. S3 bucket encryption # To be able to export the image to an S3 bucket, the bucket needs to be encrypted with the default AWS managed KMS key for S3 (SSE-S3). Otherwise the export will fail with an InsufficientPermissions exception.\nInstance Metadata Service (IMDS) # AWS encourages the use of IMDSv2 over IMDSv1. Version 2 is more secure, but requires some adjustments to any existing script using IMDSv1 when querying the instance metadata.\nIMDSv1:\ncurl http://169.254.169.254/ IMDSv2:\nTOKEN=$(curl -X PUT \u0026#34;http://169.254.169.254/latest/api/token\u0026#34; -H \u0026#34;X-aws-ec2-metadata-token-ttl-seconds: 21600\u0026#34;) curl -H \u0026#34;X-aws-ec2-metadata-token: $TOKEN\u0026#34; -v http://169.254.169.254/ Basic check to see if IMDSv1 or IMDSv2 is enabled:\nif [ -z $(curl -s http://169.254.169.254/) ]; then echo \u0026#34;Instance has been configured to use IMDSv2.\u0026#34;; fi More information can be found here\nAnother setting that will need adjustment when using Image Builder for building a container image, is setting the Metadata Hop Limit (HttpPutResponseHopLimit) to 2 or 3.\nMore information on the IMDS options can be found here.\nConclusion # My goal with this post was to show you how you can start using Image Builder to automate creating your custom AMIs or container images, and help you take that initial hurdle to start looking into Image Builder.\nIt also shows the issues I ran into while implementing Image Builder for a project I\u0026rsquo;m working on, and how to overcome those.\nIf you have any feedback on this post, please reach out to me.\n","date":"6 November 2024","externalUrl":null,"permalink":"/2024/11/06/creating-an-ami-with-image-builder/","section":"Posts","summary":"The code that accompanies this blogpost can be found here\nUpdate 2024-12-07: In the examples I use Amazon Linux 2023, which cannot be exported to other formats. I’ve changed the code in the repository to use an Ubuntu 24.04 base image, which can be exported. I’ve been working with AWS Image Builder a lot more over the last couple of months, while replacing a Packer setup that was run on a Windows laptop, with Image Builder.\n","title":"Creating an AMI with Image Builder","type":"posts"},{"content":"","date":"20 December 2023","externalUrl":null,"permalink":"/tags/aws-instance-scheduler/","section":"Tags","summary":"","title":"AWS Instance Scheduler","type":"tags"},{"content":"Recently I\u0026rsquo;ve started playing a game (Satisfactory, for those who want to know) with my two kids. To make it easier to play together, I\u0026rsquo;ve set up a dedicated server on an EC2 instance so the game is also available when the host is not available.\nTo keep the costs down a bit, I was looking for a way to shut down the instance during the hours we wouldn\u0026rsquo;t be playing anyway. That\u0026rsquo;s when I came across Instance Scheduler on AWS.\nThe Instance Scheduler on AWS solution automates the starting and stopping of Amazon Elastic Compute Cloud (Amazon EC2) and Amazon Relational Database Service (Amazon RDS) instances.\nThis solution helps reduce operational costs by stopping resources that are not in use and starting resources when their capacity is needed. For example, a company can use Instance Scheduler on AWS in a production environment to automatically stop instances outside of business hours every day. If you leave all of your instances running at full utilization, this solution can result in up to 70% cost savings for those instances that are only necessary during regular business hours (weekly utilization reduced from 168 hours to 50 hours).\nInstance Scheduler on AWS leverages Amazon Web Services (AWS) resource tags and AWS Lambda to automatically stop and restart instances across multiple AWS Regions and accounts on a customer-defined schedule. This solution also allows you to use hibernation for stopped Amazon EC2 instances.\nThis solution can be deployed in a single account, or in a central account, managing multiple accounts. Since I\u0026rsquo;m using an AWS Organization, I\u0026rsquo;ve deployed the main stack in a hub account, and the remote stack in the account that hosts the dedicated server instance.\nTo deploy Instance Scheduler (version 1.5.3 at the time of writing), go to the AWS CloudFormation console in your desired account and create a new stack using new resources. Paste the link to the CloudFormation Instance Scheduler template, as linked on the solution page, in the Amazon S3 URL field and click Next.\nOn the next page, you can configure the solution. Give the stack a meaningful name, and set the options you need. For mu configuration, I\u0026rsquo;ve set the default timezone to Europe/Amsterdam, set the option This account to No since I won\u0026rsquo;t be hosting resources in the hub account, set the Namespace, and set Use AWS Organizations to Yes. When using AWS Organizations, provide the organization ID in the Organization Id/Remote Account Ids field, otherwise provide a comma separated list of account IDs you want to manage through this account. If you forget to provide a value for this, you will get an error on the schedulereventbuspolicy resource during deployment.\nIf you want to manage resources in other regions than where you\u0026rsquo;re deploying the stack in, also make sure to enter those in the Region(s) field.\nWhen you\u0026rsquo;re done with this initial configuration, click on Next, review the settings on the Configure stack options page and click Next again.\nDo a last review of the configuration, don\u0026rsquo;t forget to tick the box to acknowledge that AWS CloudFormation might create IAM resources, and click on Submit.\nThe stack will create the resources for the solution, which takes a couple of minutes.\nOnce the resources have been deployed, we can create a schedule for the server.\nThe schedule is configured in DynamoDB. The CloudFormation stack created several DynamoDB tables. The schedules and periods used on the schedule are in a table names InstanceScheduler-main-ConfigTable-XXXXXXXXXXXX.\nI\u0026rsquo;ve created the following periods and schedule in the table.\nPeriods used:\nWorkdays period:\n{ \u0026#34;type\u0026#34;: { \u0026#34;S\u0026#34;: \u0026#34;period\u0026#34; }, \u0026#34;name\u0026#34;: { \u0026#34;S\u0026#34;: \u0026#34;satisfactory-workdays\u0026#34; }, \u0026#34;begintime\u0026#34;: { \u0026#34;S\u0026#34;: \u0026#34;17:00\u0026#34; }, \u0026#34;description\u0026#34;: { \u0026#34;S\u0026#34;: \u0026#34;Satisfactory Dedicated Server - workdays \u0026#34; }, \u0026#34;endtime\u0026#34;: { \u0026#34;S\u0026#34;: \u0026#34;23:59\u0026#34; }, \u0026#34;weekdays\u0026#34;: { \u0026#34;SS\u0026#34;: [ \u0026#34;mon-thu\u0026#34; ] } } Weekends period \u0026lsquo;on\u0026rsquo;:\n{ \u0026#34;type\u0026#34;: { \u0026#34;S\u0026#34;: \u0026#34;period\u0026#34; }, \u0026#34;name\u0026#34;: { \u0026#34;S\u0026#34;: \u0026#34;satisfactory-weekends-on\u0026#34; }, \u0026#34;begintime\u0026#34;: { \u0026#34;S\u0026#34;: \u0026#34;12:00\u0026#34; }, \u0026#34;description\u0026#34;: { \u0026#34;S\u0026#34;: \u0026#34;Satisfactory Dedicated Server - weekends\u0026#34; }, \u0026#34;endtime\u0026#34;: { \u0026#34;S\u0026#34;: \u0026#34;23:59\u0026#34; }, \u0026#34;weekdays\u0026#34;: { \u0026#34;SS\u0026#34;: [ \u0026#34;fri-sun\u0026#34; ] } } Weekends period \u0026lsquo;off\u0026rsquo;:\n{ \u0026#34;type\u0026#34;: { \u0026#34;S\u0026#34;: \u0026#34;period\u0026#34; }, \u0026#34;name\u0026#34;: { \u0026#34;S\u0026#34;: \u0026#34;satisfactory-weekends-off\u0026#34; }, \u0026#34;begintime\u0026#34;: { \u0026#34;S\u0026#34;: \u0026#34;00:00\u0026#34; }, \u0026#34;description\u0026#34;: { \u0026#34;S\u0026#34;: \u0026#34;Satisfactory Dedicated Server - weekends\u0026#34; }, \u0026#34;endtime\u0026#34;: { \u0026#34;S\u0026#34;: \u0026#34;02:00\u0026#34; }, \u0026#34;weekdays\u0026#34;: { \u0026#34;SS\u0026#34;: [ \u0026#34;sat-sun\u0026#34; ] } } These periods turn the server on on weekdays between 17:00 and midnight, and for the weekends, turn it on on Fridays, Saturdays and Sundays at noon, until 02:00 on Saturdays and Sundays, and until midnight on Sundays, when combined into a schedule:\n{ \u0026#34;type\u0026#34;: { \u0026#34;S\u0026#34;: \u0026#34;schedule\u0026#34; }, \u0026#34;name\u0026#34;: { \u0026#34;S\u0026#34;: \u0026#34;satisfactory\u0026#34; }, \u0026#34;description\u0026#34;: { \u0026#34;S\u0026#34;: \u0026#34;Schedule for Satisfactory Dedicated Server\u0026#34; }, \u0026#34;periods\u0026#34;: { \u0026#34;SS\u0026#34;: [ \u0026#34;satisfactory-weekends-off\u0026#34;, \u0026#34;satisfactory-weekends-on\u0026#34;, \u0026#34;satisfactory-workdays\u0026#34; ] } } Once that\u0026rsquo;s done, switch over to the account and region the dedicated server is in, and deploy the CloudFormation instance schedule remote template in CloudFormation.\nIn the stack details, specify the same Namespace used in the stack in the hub account, provide the account ID of the hub account in Hub Account ID and set Use AWS Organizations to Yes because that\u0026rsquo;s what I\u0026rsquo;m using in the hub account.\nWhen that stack has deployed, the server needs a tag so Instance Scheduler knows to manage this instance, and what schedule to use. The tag we need to set on the EC2 instance is Schedule, with the value of the schedule we created, satisfactory.\nAnd that\u0026rsquo;s that!\nAn alternative AWS provides is Resource Scheduler. With Resource Scheduler, you can \u0026lsquo;only\u0026rsquo; schedule EC2 instances, and they are not checked for the desired state, but might fit your use case better. I chose for Instance Scheduler to see how it works, and because I like solutions that can be managed from a hub account.\nFor this post, I\u0026rsquo;ve written about a personal dedicated game server, a fun project. But in environments where you don\u0026rsquo;t need your resources running all day (like test and dev) or even in production environments where you need to have an instance available only at specific times, this can be helpful to reduce costs.\n","date":"20 December 2023","externalUrl":null,"permalink":"/2023/12/20/dedicated-game-server-and-aws-instance-scheduler/","section":"Posts","summary":"Recently I’ve started playing a game (Satisfactory, for those who want to know) with my two kids. To make it easier to play together, I’ve set up a dedicated server on an EC2 instance so the game is also available when the host is not available.\n","title":"Dedicated game server and AWS Instance Scheduler","type":"posts"},{"content":"The code that accompanies this blogpost can be found here\nRecently I\u0026rsquo;ve been looking into patching Windows servers that have dependencies between them, using AWS Systems Manager.\nThe use-case was an application that exists of web servers, middleware servers and a database server.\nThe web servers have connections open to the database server, and the middleware servers run processes that get information from the database server.\nThe servers were patched manually, by stopping the services on the web servers and middleware servers first and checking that all middleware services were stopped, before stopping the databases. Once that was done, the servers were updated. After patching, the databases were first brought back online, before starting the middleware services and the web services again.\nTo set this up, I created some PowerShell scripts (with a little bit of SSM variable flavour) to be run on the instances to stop and start the services, as well as checking the services before continuing to the next step. These scripts were put as SSM documents, to be called from an automation document.\nExample script (Start-Components.ps1):\ntry { $_serverRole = \u0026#34;{{ServerRole}}\u0026#34; # This is an SSM variable reference $_fqdn = \u0026#34;$((Get-WmiObject Win32_ComputerSystem).DNSHostName).$((Get-WmiObject Win32_ComputerSystem).Domain)\u0026#34; Write-Output \u0026#34;[INF] Starting Components on $($_fqdn) with server role \u0026#39;$_serverRole\u0026#39;\u0026#34; switch ($_serverRole) { Web { Write-Output \u0026#34;[INF] Setting Startup Type for web services where the current StartType is Manual to Automatic and starting them.\u0026#34; Get-Service iisadmin | Where-Object StartType -eq \u0026#34;Manual\u0026#34; | Set-Service -StartupType Automatic -Status Running Get-Service w3svc | Where-Object StartType -eq \u0026#34;Manual\u0026#34; | Set-Service -StartupType Automatic -Status Running } Middleware { Write-Output \u0026#34;[INF] Doing stuff to enable the middleware services to start.\u0026#34; # Your code here } Database { Write-Output \u0026#34;[INF] Setting Startup Type for all database services where the current StartType is Manual to Automatic and starting them.\u0026#34; Get-Date -Format \u0026#34;yyyy-MM-dd HH:mm:ss\u0026#34; Get-Service *sql* | Where-Object StartType -eq \u0026#34;Manual\u0026#34; | Set-Service -StartupType Automatic -Status Running Write-Output \u0026#34;[INF] Making sure all database services are started before continuing.\u0026#34; # When there are no services that match the name, the while loop will not be entered. while (Get-Service *sql* | Where-Object Status -ne Running) { Write-Output \u0026#34;[DEB] [$(Get-Date -Format \u0026#34;yyyy-MM-dd HH:mm:ss\u0026#34;)] Not all database services have started yet. Waiting a little longer.\u0026#34; Start-Sleep -Seconds 60 } } Default { } } } catch { Write-Output \u0026#34;[ERR] Failed to start components!\u0026#34; Write-Error $Error[0] -ErrorAction Continue exit 1 } Which is consumed to create an SSM document using Terraform:\nresource \u0026#34;aws_ssm_document\u0026#34; \u0026#34;patching_start_components\u0026#34; { name = \u0026#34;Patching-StartComponents\u0026#34; document_type = \u0026#34;Command\u0026#34; target_type = \u0026#34;/AWS::EC2::Instance\u0026#34; content = jsonencode({ schemaVersion = \u0026#34;2.2\u0026#34; description = \u0026#34;Patching Post-install Start Components Document\u0026#34; parameters = { ServerRole = { type = \u0026#34;String\u0026#34; description = \u0026#34;Role of the server (Web, Middleware, Database, None)\u0026#34; default = \u0026#34;None\u0026#34; allowedValues = [ \u0026#34;Web\u0026#34;, \u0026#34;Middleware\u0026#34;, \u0026#34;Database\u0026#34;, \u0026#34;None\u0026#34;, ] } } mainSteps = [ { action = \u0026#34;aws:runPowerShellScript\u0026#34; name = \u0026#34;StartComponents\u0026#34; precondition = { StringEquals = [ \u0026#34;platformType\u0026#34;, \u0026#34;Windows\u0026#34; ] } inputs = { runCommand = split(\u0026#34;\\n\u0026#34;, file(\u0026#34;${path.cwd}/powershell_scripts/Start-Components.ps1\u0026#34;)) } } ] }) } Using an automation document, we can orchestrate the flow of patching. In the example code, I\u0026rsquo;ve also included a method to patch servers of the same function at different times. For this, the option PatchWindow has been added, with allowed values Monday and Wednesday. The output of each step is redirected to an encrypted CloudWatch log-group.\nresource \u0026#34;aws_ssm_document\u0026#34; \u0026#34;patching_automation\u0026#34; { name = \u0026#34;Patching-Automation\u0026#34; document_type = \u0026#34;Automation\u0026#34; document_format = \u0026#34;YAML\u0026#34; content = \u0026lt;\u0026lt;EOT description: |- # Patching Automation This script provides a staged patching experience. Services are stopped in a specific order on specific instances after which patching is run, and services are started again on servers in reverse order. schemaVersion: \u0026#39;0.3\u0026#39; parameters: PatchWindow: type: String allowedValues: - Monday - Wednesday description: Patch-window to run for. Determines which servers are affected. mainSteps: - name: StopWebServerServices action: \u0026#39;aws:runCommand\u0026#39; inputs: DocumentName: ${aws_ssm_document.patching_stop_components.name} Targets: - Key: \u0026#39;tag:ServerRole\u0026#39; Values: - Web - Key: \u0026#39;tag:PatchWindow\u0026#39; Values: - \u0026#39;{{PatchWindow}}\u0026#39; Parameters: ServerRole: Web CloudWatchOutputConfig: CloudWatchLogGroupName: ${aws_cloudwatch_log_group.automated_patching.name} CloudWatchOutputEnabled: true description: Stop the services on the web servers nextStep: StopMiddlewareServices onFailure: \u0026#39;step:StartWebServerServices\u0026#39; - name: StopMiddlewareServices action: \u0026#39;aws:runCommand\u0026#39; inputs: DocumentName: ${aws_ssm_document.patching_stop_components.name} Targets: - Key: \u0026#39;tag:ServerRole\u0026#39; Values: - Middleware - Key: \u0026#39;tag:PatchWindow\u0026#39; Values: - \u0026#39;{{PatchWindow}}\u0026#39; Parameters: ServerRole: Middleware CloudWatchOutputConfig: CloudWatchLogGroupName: ${aws_cloudwatch_log_group.automated_patching.name} CloudWatchOutputEnabled: true description: Stop the services on the middleware servers nextStep: StopDatabaseServices onFailure: \u0026#39;step:StartMiddlewareServices\u0026#39; - name: StopDatabaseServices action: \u0026#39;aws:runCommand\u0026#39; inputs: DocumentName: ${aws_ssm_document.patching_stop_components.name} Targets: - Key: \u0026#39;tag:ServerRole\u0026#39; Values: - Database - Key: \u0026#39;tag:PatchWindow\u0026#39; Values: - \u0026#39;{{PatchWindow}}\u0026#39; Parameters: ServerRole: Database CloudWatchOutputConfig: CloudWatchLogGroupName: ${aws_cloudwatch_log_group.automated_patching.name} CloudWatchOutputEnabled: true description: Stop the services on the database servers nextStep: PatchServers onFailure: \u0026#39;step:StartDatabaseServices\u0026#39; - name: PatchServers action: \u0026#39;aws:runCommand\u0026#39; inputs: DocumentName: AWS-RunPatchBaseline Targets: # Uncomment the following lines to only patch specific server-roles # - Key: \u0026#39;tag:ServerRole\u0026#39; # Values: # - Web # - Middleware # - Database - Key: \u0026#39;tag:PatchWindow\u0026#39; Values: - \u0026#39;{{PatchWindow}}\u0026#39; Parameters: Operation: Install RebootOption: RebootIfNeeded CloudWatchOutputConfig: CloudWatchLogGroupName: ${aws_cloudwatch_log_group.automated_patching.name} CloudWatchOutputEnabled: true description: Patch the servers nextStep: StartDatabaseServices onFailure: Abort - name: StartDatabaseServices action: \u0026#39;aws:runCommand\u0026#39; inputs: DocumentName: ${aws_ssm_document.patching_start_components.name} Targets: - Key: \u0026#39;tag:ServerRole\u0026#39; Values: - Database - Key: \u0026#39;tag:PatchWindow\u0026#39; Values: - \u0026#39;{{PatchWindow}}\u0026#39; Parameters: ServerRole: Database CloudWatchOutputConfig: CloudWatchLogGroupName: ${aws_cloudwatch_log_group.automated_patching.name} CloudWatchOutputEnabled: true description: Start the services on the database servers nextStep: StartMiddlewareServices onFailure: Abort - name: StartMiddlewareServices action: \u0026#39;aws:runCommand\u0026#39; inputs: DocumentName: ${aws_ssm_document.patching_start_components.name} Targets: - Key: \u0026#39;tag:ServerRole\u0026#39; Values: - Middleware - Key: \u0026#39;tag:PatchWindow\u0026#39; Values: - \u0026#39;{{PatchWindow}}\u0026#39; Parameters: ServerRole: Middleware CloudWatchOutputConfig: CloudWatchLogGroupName: ${aws_cloudwatch_log_group.automated_patching.name} CloudWatchOutputEnabled: true description: Start the services on the middleware servers nextStep: StartWebServerServices onFailure: Abort - name: StartWebServerServices action: \u0026#39;aws:runCommand\u0026#39; inputs: DocumentName: ${aws_ssm_document.patching_start_components.name} Targets: - Key: \u0026#39;tag:ServerRole\u0026#39; Values: - Web - Key: \u0026#39;tag:PatchWindow\u0026#39; Values: - \u0026#39;{{PatchWindow}}\u0026#39; Parameters: ServerRole: Web CloudWatchOutputConfig: CloudWatchLogGroupName: ${aws_cloudwatch_log_group.automated_patching.name} CloudWatchOutputEnabled: true description: Start the services on the web servers isEnd: true EOT } The automation document allows for some error-handling as well. As you can see in the example, when the step StopMiddlewareServices fails, it will skip to step StartMiddlewareServices (defined with the line onFailure: 'step:StartMiddlewareServices') and will proceed from there.\nOnce we have the automation document in place, we can create maintenance windows with an associated task, to execute the automation document for that triggers automatically executing the automation document.\nresource \u0026#34;aws_ssm_maintenance_window\u0026#34; \u0026#34;install_window_monday\u0026#34; { enabled = true name = \u0026#34;patch-window-monday\u0026#34; schedule = local.patching.cron_patching_monday duration = 4 cutoff = 2 } resource \u0026#34;aws_ssm_maintenance_window_task\u0026#34; \u0026#34;task_install_patches_monday\u0026#34; { window_id = aws_ssm_maintenance_window.install_window_monday.id name = \u0026#34;install-patches-monday\u0026#34; task_type = \u0026#34;AUTOMATION\u0026#34; task_arn = aws_ssm_document.patching_automation.name priority = 5 task_invocation_parameters { automation_parameters { document_version = \u0026#34;$LATEST\u0026#34; parameter { name = \u0026#34;PatchWindow\u0026#34; values = [\u0026#34;Monday\u0026#34;] } } } } In this example, instances with a tag PatchWindow with a value of Monday will be targeted for the maintenance task.\nAfter applying the code to your environment, instances can be included by setting two tags on them. PatchWindow determines the maintenance window the instance will be included in. In this example, valid values are Monday and Wednesday. ServerRole determines which actions in the PowerShell scripts will be taken. In this example, valid values are Web, Middleware, Database or None.\n","date":"13 November 2023","externalUrl":null,"permalink":"/2023/11/13/automating-patching-with-aws-systems-manager/","section":"Posts","summary":"The code that accompanies this blogpost can be found here\nRecently I’ve been looking into patching Windows servers that have dependencies between them, using AWS Systems Manager.\nThe use-case was an application that exists of web servers, middleware servers and a database server.\n","title":"Automating patching with AWS Systems Manager","type":"posts"},{"content":"","date":"13 November 2023","externalUrl":null,"permalink":"/tags/aws-systems-manager/","section":"Tags","summary":"","title":"AWS Systems Manager","type":"tags"},{"content":"","date":"13 November 2023","externalUrl":null,"permalink":"/tags/preferred-scripting-language/","section":"Tags","summary":"","title":"Preferred Scripting Language","type":"tags"},{"content":"","date":"5 June 2023","externalUrl":null,"permalink":"/tags/aws/","section":"Tags","summary":"","title":"AWS","type":"tags"},{"content":"","date":"5 June 2023","externalUrl":null,"permalink":"/tags/aws-application-migration-service/","section":"Tags","summary":"","title":"AWS Application Migration Service","type":"tags"},{"content":"","date":"5 June 2023","externalUrl":null,"permalink":"/tags/aws-migration-hub/","section":"Tags","summary":"","title":"AWS Migration Hub","type":"tags"},{"content":"Earlier this year I\u0026rsquo;ve worked on a project where we had to migrate 42 servers from a data center to AWS as part of an AWS Migration Acceleration Program (MAP) deal. There was some pressure, since the contract with the data center would end about 2 months later and the customer didn\u0026rsquo;t want to renew or extend the current contract with the data center.\nDuring this project I learned some things I think are valuable and would like to share. This might save you some time and/or frustration when you\u0026rsquo;re working on a similar project.\nDisclaimer: this is not meant to be a full migration guide, but rather things I encountered and want to share. Take from this what you can use, and create a migration plan for your specific situation.\nThe tools used # For this project, the following tools were used:\nAWS Migration Hub AWS Application Migration Service Terraform (or any IaC tool of your choice) Scripting-language of your choice (I used PowerShell) Bash-scripting (the servers were running Linux) Step one: Inventarisation # To know what exactly you\u0026rsquo;re dealing with, it\u0026rsquo;s important to make an inventarisation of the environment you\u0026rsquo;ll be migrating, and everything it makes use of or is used by. Having a clear overview makes it easier to spot potential issues, plan ahead and help set up a realistic time frame for the migration.\nNetwork / subnets\nGet all information about the current networks and subnets the servers are using, are the servers using static or dynamic IP addresses, are there any VPN connections, do we need to take any allow-listings (both inbound and outbound) into account and what public IP addresses are in use, if any. The customer might even have their own public IP range which they want to (partially) move to AWS.\nFirewall rules\nTry to get a complete overview of all firewall rules that are in place. This helps in determining which servers should be accessible from where, should be able to connect to where, and might also help you spot issues with the current setup so you can mitigate those in the new setup.\nTraffic flows between servers\nThe firewall rules might be of use for that, but traffic between private subnets is often unrestricted. Knowing the traffic flows between servers will help you set up more restrictive (and safer) security groups within AWS.\nDNS domains\nDoes the customer want to move DNS domains to AWS? Are there DNS-records that point to the servers and need to be changed? If there are changes to be made to DNS records and/or domains, some planning needs to be done to make sure that during the actual migration, you don\u0026rsquo;t have to wait hours or days before a change has been propagated throughout the internet.\nCertificates\nDo the servers or any applications running on them use any certificate and how are they managed? Knowing this can help determine how to expose an application to the internet; can you use an Application Load Balancer or is the certificate managed through an automated system which runs on the server itself? In the latter case, you either have to use a Network Load Balancer, connect the server \u0026lsquo;directly\u0026rsquo; to the internet (bad practice!) or change how the certificate is managed, which might have a big impact on the work that needs to be done.\nBackup RTO and RPO\nFor setting up the new environment, it\u0026rsquo;s important to know what RPO and RTO the customer requires for which service / application / server. Setting up AWS Backup in advance makes it easier to enable it during or just after the migration.\nSoftware license requirements\nSome software vendors use the MAC-address of a server to bind their licenses to. If that\u0026rsquo;s the case, some additional actions need to be taken to ensure you don\u0026rsquo;t have to keep changing your license registration with such vendors.\nOS versions being used and patch level\nBefore you start working on preparing for the migration, it\u0026rsquo;s wise to know which OSes are being used, and what versions. When you encounter older OS versions, there might be more work involved installing the necessary agents, or it might even be impossible. Also, knowing the current patch-level of the OSes and how patching is managed is important to know for the new environment in AWS.\nSoftware used\nSpecific products and/or versions are eligible for additional discounts in AWS MAP, like commercial databases, SAP and more.\nStep two: Make a plan # Once you\u0026rsquo;ve got at least most of the information, it\u0026rsquo;s time to start making a plan.\nMake an IP-plan for the new environment\nYou won\u0026rsquo;t always be able to keep the current IP-addresses and creating a new IP-plan is important for setting up the new network and subnets, with proper sizing.\nDetermine the security groups to create\nMake sure you know the needed security groups to allow traffic between servers, and allow the required inbound and outbound traffic. Determine which security groups to make and what servers to attach them to.\nMake sure you have your MAP tag number (MPE ID)\nThis is needed when deploying resources to get the discount. Determine how you will apply the tags and what resources might need alternate tags.\nDetermine the use of a launch template (highly recommended!)\nAWS Application Migration Service makes use of launch templates. Determining if you\u0026rsquo;re going to use it and what settings you want to specify in it, helps to gather possible additional info.\nDetermine the order of migration\nMake an initial order of migration, and keep validating that order until the actual migration.\nStep three: Preparations # Once you\u0026rsquo;ve created an initial plan to work from, it\u0026rsquo;s time to start the actual preparations.\nSet up the the management account listed in the Migration Plan (the management account that\u0026rsquo;s part of the MAP deal) and activate the Cost Allocation Tag required for MAP 2.0. This info should be made available to you by your AWS representative for the MAP deal.\nSet up AWS Migration Hub and either install the Discovery Agent on the servers you\u0026rsquo;re migrating, or, if you have access to the hypervisor layer, install the appliance. More info on these can be found in the AWS Documentation for AWS Migration Hub\nNext, deploy the infrastructure you\u0026rsquo;re migrating the servers to, including an initial security group to assign to the launch template, and an Instance Profile with the appropriate permissions. Next, create VPC Endpoints for the services SSM uses; this way you should be able to access the servers even when they cannot connect to the internet (which is probably the case during the test phases).\nAlso make sure that you tag everything with the map-migrated tag, also for the infrastructure. With Terraform, you can set this using the AWS provider parameter default_tags. More info on the exact tag value should be available through the AWS MAP channel.\nOnce you\u0026rsquo;ve got the basic infrastructure set up, initialise the AWS Application Migration Service. During the initialisation, also make sure to set up the default launch template. Make sure the MAP tag is added with the appropriate value, the security group, instance profile, network subnet, et cetera.\nAfter AWS Application Migration Service has been initialised, the Replication Agent can be installed on the servers to be migrated. The replication agent uses TCP port 1500 to connect to the AWS Application Migration Service, so make sure any firewall allows TCP port 1500 outbound for the source servers.\nNB: The Replication Agent requires the Linux headers to install. For older Linux-versions this could mean you have to locate the Linux headers for the specific release, since they might no longer be available through the distributions update manager.\nInstall the AWS SSM Agent on the source servers. This is helpful to be able to connect to the server through the AWS Console using Session Manager, or even using the Session Manager Plugin for the AWS CLI. During testing and the actual migration, this can prove useful when you\u0026rsquo;re running into issues with any server.\nOnce the servers have started replicating, we have to play the waiting game.\nUntil the servers have finished their initial replication, there\u0026rsquo;s not much to be done. The time it takes before the initial replication is done, is dependent on the speed of the internet connection, the total amount of data to be replicated, as well as the number of changes to the filesystem the source systems have. The AWS Application Migration Service console gives an estimate of the time required to complete replication, which is constantly updated.\nStep four: Test, test, test! # When all (or at least the ones you want to start with) servers have replicated, you can start testing.\nIn the AWS Application Migration Service console, select one or more servers to test, and launch test instances.\nMake sure that the test-servers are unable to contact live servers, so they do not contaminate any production environment.\nCreate a migration-script per server. Do multiple test-runs to check and improve the migration-scripts.\nDuring testing, you might encounter software that can throw a wrench in the migration, like corosync and pacemaker. When you encounter such software, determine if you still need it and take action accordingly to mitigate any possible issues that might arise by keeping those configurations as they are.\nEvaluate of your intended order of migration is valid. During testing you might find a different order is needed.\nCreate waves based on the order of migration for a simpler orchestration during the actual migration.\nDo at least one full test-migration. This helps determine how much time is needed for the full migration. This is important for how much down-time you\u0026rsquo;ll have, which needs to be communicated with the customer and any users of the application(s), as well as help in deciding the moment of the actual migration, the number of people working on the migration, when the test-persons should be able to start testing the application after migration, et cetera.\nIf you\u0026rsquo;re moving any server from being directly exposed to the internet, to being fronted by a load balancer, test the load balancer configuration as best you can.\nOnce you\u0026rsquo;re done testing a source server, mark it as \u0026lsquo;Ready for cut-over\u0026rsquo; in AWS Application Migration Service.\nIf there are DNS changes to be made, prepare for them; lower TTL values for records that need to be changed, and prepare any domain that needs to be moved to Route53, or even move them in advance if possible.\nStep five: The real deal # This is what you\u0026rsquo;ve been testing for!\nShut down any running services on the live servers, especially databases, and wait for the last changes to be replicated to AWS.\nStart migrating in waves.\nMake sure your security groups have the proper access (they should at least be reachable for the group of test-users)\nHave your test-group test as early as possible and have a select group of people report on any findings. Triage what needs to be fixed right away, and what can wait. Have product owners participate in this where possible.\nMark servers that have been given the green light as \u0026lsquo;Finalize cut-over\u0026rsquo; in AWS Application Migration Service to indicate they\u0026rsquo;re finished.\nTurn on VPC Flow Logs to help troubleshoot any network-issues during the migration.\nStep six: The aftermath # Once the migration has been finished successfully, there\u0026rsquo;s a few more things that need to be done.\nTurn off the old servers, or at the very least make sure that the applications will not be enabled again.\nMake sure the servers and services are being backed up in AWS.\nMark the migrated servers as \u0026lsquo;Mark as archived\u0026rsquo; in AWS Application Migration Service.\nRemove any software from the servers that was specifically needed for the data center architecture (e.g. VMware tool, Azure tools)\nPoints of attention # During both testing and the actual migration, when launching multiple (bigger) instances at the same time, one or more instances might respond badly/have weird issues. In that case, stop the instance(s) in the AWS Console, wait a minute or two, and start it up again. The reason for this is that the underlying host has issues allocating the proper resources to the instance. Stopping the instance and starting it again relocates the instance to a host that has sufficient resources available for the instance.\nIf you\u0026rsquo;re using user_data in your launch template(s), this will only be run when the server has a working network connection. If a server has no working network connection, user_data cannot be retrieved from the instance metadata and cannot be run.\nMake sure that the customer tests the application(s) during migration and sign off on them. Ultimately, it\u0026rsquo;s the customers responsibility to determine if an application is working as intended and if all data is correctly transferred.\n","date":"5 June 2023","externalUrl":null,"permalink":"/2023/06/05/lessons-learned-from-migrating-42-servers-to-aws/","section":"Posts","summary":"Earlier this year I’ve worked on a project where we had to migrate 42 servers from a data center to AWS as part of an AWS Migration Acceleration Program (MAP) deal. There was some pressure, since the contract with the data center would end about 2 months later and the customer didn’t want to renew or extend the current contract with the data center.\n","title":"Lessons learned from migrating 42 servers to AWS","type":"posts"},{"content":"","date":"5 June 2023","externalUrl":null,"permalink":"/tags/preferred-scripting-language-i-used-powershell/","section":"Tags","summary":"","title":"Preferred Scripting Language (I Used PowerShell)","type":"tags"},{"content":"So, a couple of weeks ago a coworker mentioned the Community Builder program was open for sign-up again. So I gave it a try.\nToday I got a message confirming I\u0026rsquo;m now officially an AWS Community Builder (Security \u0026amp; Identity)! :-D\n","date":"25 February 2023","externalUrl":null,"permalink":"/2023/02/25/aws-community-builder/","section":"Posts","summary":"So, a couple of weeks ago a coworker mentioned the Community Builder program was open for sign-up again. So I gave it a try.\nToday I got a message confirming I’m now officially an AWS Community Builder (Security \u0026 Identity)! :-D\n","title":"AWS Community Builder","type":"posts"},{"content":"","date":"25 February 2023","externalUrl":null,"permalink":"/tags/community-builder/","section":"Tags","summary":"","title":"Community Builder","type":"tags"},{"content":" How to enable and configure Security Hub for PCI DSS compliancy checks using Terraform # This article is also posted here\nFor companies that deal with payment card holder\u0026rsquo;s data (CHD) and sensitive authentication data (SAD), obtaining PCI DSS compliancy is vital. It provides a set of standards and controls which help to prevent unauthorised access and data-loss. Often, adherence to these standards is a requirement for obtaining licenses that have to do with financial services, such as banking.\nExcerpt from PCI DSS v3.2.1 on AWS:\nThe purpose of the PCI DSS is to protect cardholder data (CHD) and sensitive authentication data (SAD) from unauthorized access and loss. Cardholder data consists of the Primary Account Number (PAN), cardholder name, expiration date, and service code. Sensitive authentication data (SAD) includes the full track data (magnetic-stripe data or equivalent on a chip), CAV2/CVC2/CVV2/CID, and PINs/PIN blocks.\nYou can check if an AWS service is PCI compliant on the AWS Services in Scope by Compliance Program - PCI DSS page. Also, AWS publishes assessment reports of their certifications and attestation in AWS Artifact, including the report for PCI DSS.\nAside from AWS being responsible for the compliancy of their infrastructure and services (security \u0026lsquo;of\u0026rsquo; the cloud), the customer is responsible for the compliancy of the components used and the data processed (security \u0026lsquo;in\u0026rsquo; the cloud), as per the AWS Shared Responsibility Model.\nTable of contents\nAWS services Organisation settings Setting up AWS Config Aggregation (delegated admin) account Additional accounts Setting up AWS Security Hub Admin account Additional accounts Executing the code Conclusion AWS services # AWS provides tools to help us, the customer, prepare for our PCI DSS v3.2.1 assessment.\nAWS Security Hub is a cloud security posture management service, that performs security best practice checks, aggregates alerts, end enables automated remediation. This service provides a PCI DSS compliance package, that checks a set of controls related to PCI DSS requirements, to give an overview of your readiness for an assessment.\nIn order to be able to check all controls, AWS Config needs to be configured for Security Hub to be able to perform the PCI DSS checks.\nI\u0026rsquo;ve written two modules, one for Security Hub and one for Config, to assist in configuring these services using Terraform, which are included in this sample repository.\nOrganisation settings # In AWS Organizations, AWS Config and Security Hub must have been enabled for delegation. This can be done using the AWS console, or using AWS CLI:\naws organizations enable-aws-service-access --service-principal config.amazonaws.com aws organizations enable-aws-service-access --service-principal config-multiaccountsetup.amazonaws.com aws organizations enable-aws-service-access --service-principal securityhub.amazonaws.com This can also be done using Terraform, when creating the organisation. For example:\nresource \u0026#34;aws_organizations_organization\u0026#34; \u0026#34;org\u0026#34; { aws_service_access_principals = [ \u0026#34;cloudtrail.amazonaws.com\u0026#34;, \u0026#34;config.amazonaws.com\u0026#34;, \u0026#34;config-multiaccountsetup.amazonaws.com\u0026#34;, \u0026#34;securityhub.amazonaws.com\u0026#34;, # any additional services to enable ] enabled_policy_types = [ \u0026#34;BACKUP_POLICY\u0026#34;, \u0026#34;SERVICE_CONTROL_POLICY\u0026#34;, \u0026#34;TAG_POLICY\u0026#34;, ] feature_set = \u0026#34;ALL\u0026#34; } Setting up AWS Config # To configure AWS Config, we have to set it up in every accounts and in every region we want to enable it. Also, setting up an aggregator helps to get an overview of all accounts and regions in a single place.\nAggregation (delegated admin) account # In the example code, we first set up AWS Config in the account that will do the aggregation, in the primary region.\nWe pass providers for the account we are setting up (aws.account) and the management account (aws.management). The last one is needed to be able to delegate admin to the account.\nmodule \u0026#34;config_aggregator_primary_region\u0026#34; { source = \u0026#34;./modules/config\u0026#34; providers = { aws.account = aws.security-admin aws.management = aws.management } config_bucket_name = local.config_bucket_name # Name for the bucket to create organization_id = data.aws_organizations_organization.org.id # Used for creating the SNS Topic policy config_sns_topic_prefix = var.config_sns_topic_prefix # Used for creating the IAM role policy is_aggregator = true # This is the aggregator account is_primary_region = true # This is the primary region } This sets admin delegation to the account used in the aws.account provider in that specific region, and creates the following:\nAn S3 bucket, which we use throughout the configuration an IAM role for the account A region-specific SNS Topic and corresponding policy An aggregator A recorder with delivery channel settings For a secondary region, we need to set the admin delegation again, as well as create a region-specific SNS Topic.\nmodule \u0026#34;config_aggregator_global\u0026#34; { source = \u0026#34;./modules/config\u0026#34; providers = { aws.account = aws.security-admin-us-east-1 aws.management = aws.management-us-east-1 } config_bucket_name = module.config_aggregator_primary_region.config_bucket_name # The bucket created in the primary region of the aggregator organization_id = data.aws_organizations_organization.org.id # Used for creating the SNS Topic policy config_iam_role_arn = module.config_aggregator_primary_region.config_iam_role_arn # IAM role ARN to use for the recorder is_aggregator = true # This is the aggregator account is_primary_region = false # This is a secondary region depends_on = [ module.config_aggregator_primary_region ] } By setting is_primary_region to false, the following resources will not be created:\nS3 bucket (we only need one) IAM role (IAM is global) Aggregator (we only need one) For the delivery channel, we set the S3 bucket to the one created in the aggregator primary region.\nAdditional accounts # For additional accounts, we have to configure Config as well, per region.\nNOTE: Every account and every used region in the organisation have to be configured\nSince we cannot make a provider optional, we still have to pass a provider for the management account (aws.management).\nmodule \u0026#34;config_management_primary_region\u0026#34; { source = \u0026#34;./modules/config\u0026#34; providers = { aws.account = aws.management aws.management = aws.management } config_bucket_name = module.config_aggregator_primary_region.config_bucket_name # The bucket created in the prep config_sns_topic_arn = module.config_aggregator_primary_region.config_sns_topic_arn # Region-specific SNS topic to send notifications to config_sns_topic_prefix = var.config_sns_topic_prefix # Used for creating the IAM role policy is_aggregator = false # This is not the aggregator account is_primary_region = true # This is the primary region for the account } module \u0026#34;config_management_global\u0026#34; { source = \u0026#34;./modules/config\u0026#34; providers = { aws.account = aws.management-us-east-1 aws.management = aws.management-us-east-1 } config_iam_role_arn = module.config_management_primary_region.config_iam_role_arn # IAM role is already created in the account preparation config_bucket_name = module.config_aggregator_primary_region.config_bucket_name # The bucket created in the prep config_sns_topic_arn = module.config_aggregator_global.config_sns_topic_arn # Region-specific SNS topic to send notifications to is_aggregator = false # This is not the aggregator account } In the primary region of every account (is_primary_region set to true) we create an IAM role.\nWe refer to the S3 bucket created in the primary region in the aggregator account, as well as the region-specific SNS Topics in the aggregator account for the delivery channel of the recorder for every region in the account.\nSetting up AWS Security Hub # For setting up Security Hub, we need to take a similar approach, where we first configure the account that will be the delegated admin, per region, and then set up additional accounts that already existed at the time of enabling Security Hub. In the example module, the organisation will be configured to auto-enable Security Hub for new accounts.\nAdmin account # In the example code, we first set up Security Hub in the account that will be the delegated admin, in the primary region, and again for every additional region.\nWe pass providers for:\nThe account we are setting up (aws.account, used for configuring the account) The delegated admin account (aws.admin, used for inviting the account to Security Hub) The management account (aws.management, used for delegating admin and getting info about the account from AWS Organizations). module \u0026#34;securityhub_admin_primary_region\u0026#34; { source = \u0026#34;./modules/securityhub\u0026#34; providers = { aws.account = aws.security-admin aws.admin = aws.security-admin aws.management = aws.management } securityhub_security_standards = var.securityhub_security_standards is_aggregation_region = true invite = false is_admin = true is_member = false } module \u0026#34;securityhub_admin_global\u0026#34; { source = \u0026#34;./modules/securityhub\u0026#34; providers = { aws.account = aws.security-admin-us-east-1 aws.admin = aws.security-admin-us-east-1 aws.management = aws.management-us-east-1 } securityhub_security_standards = var.securityhub_security_standards invite = false is_admin = true is_member = false depends_on = [ module.securityhub_admin_primary_region, ] } The depends_on is used to ensure the admin delegation has been completed in the primary region, before we start delegation in the additional region.\nAdditional accounts # Accounts that already existed when enabling and configuring Security Hub have to be configures in the Security Hub deployment.\nFor example, adding the management account to Security Hub:\nmodule \u0026#34;securityhub_management_primary_region\u0026#34; { source = \u0026#34;./modules/securityhub\u0026#34; providers = { aws.account = aws.management aws.admin = aws.security-admin aws.management = aws.management } securityhub_security_standards = var.securityhub_security_standards invite = false is_admin = false is_member = true depends_on = [ module.securityhub_admin_primary_region, module.securityhub_admin_global, ] } module \u0026#34;securityhub_management_global\u0026#34; { source = \u0026#34;./modules/securityhub\u0026#34; providers = { aws.account = aws.management-us-east-1 aws.admin = aws.security-admin-us-east-1 aws.management = aws.management-us-east-1 } securityhub_security_standards = var.securityhub_security_standards invite = false is_admin = false is_member = true depends_on = [ module.securityhub_admin_primary_region, module.securityhub_admin_global, ] } In the definitions for the additional accounts, the depends_on attribute is used to ensure the admin account is set up first. Otherwise we might get errors during the step that invites the account to Security Hub.\nExecuting the code # Once the accounts and regions are added to the code, we can run terraform plan to see what actions will be taken:\n\u0026gt; terraform plan # ... module.config_management_primary_region.data.aws_region.current: Reading... module.config_management_primary_region.data.aws_caller_identity.current: Reading... module.securityhub_admin_primary_region.data.aws_organizations_organization.org: Reading... data.aws_organizations_organization.org: Reading... module.config_management_primary_region.data.aws_region.current: Read complete after 0s [........] module.config_management_primary_region.data.aws_caller_identity.current: Read complete after 1s [........] module.securityhub_admin_primary_region.data.aws_caller_identity.account: Reading... module.securityhub_admin_primary_region.data.aws_region.account: Reading... module.config_aggregator_primary_region.data.aws_region.current: Reading... module.securityhub_admin_primary_region.data.aws_caller_identity.admin: Reading... module.config_aggregator_primary_region.data.aws_region.current: Read complete after 0s [........] module.securityhub_admin_primary_region.data.aws_region.account: Read complete after 0s [........] module.config_aggregator_primary_region.data.aws_caller_identity.current: Reading... module.securityhub_admin_primary_region.data.aws_caller_identity.admin: Read complete after 0s [........] module.securityhub_admin_primary_region.data.aws_caller_identity.account: Read complete after 0s [........] module.config_aggregator_primary_region.data.aws_caller_identity.current: Read complete after 0s [........] module.securityhub_admin_global.data.aws_organizations_organization.org: Reading... module.config_management_global.data.aws_caller_identity.current: Reading... module.config_management_global.data.aws_region.current: Reading... module.config_management_global.data.aws_region.current: Read complete after 0s [........] data.aws_organizations_organization.org: Read complete after 2s [........] module.securityhub_admin_primary_region.data.aws_organizations_organization.org: Read complete after 2s [........] module.config_management_global.data.aws_caller_identity.current: Read complete after 0s [........] module.securityhub_admin_global.data.aws_caller_identity.account: Reading... module.securityhub_admin_global.data.aws_caller_identity.admin: Reading... module.securityhub_admin_global.data.aws_region.account: Reading... module.securityhub_admin_global.data.aws_region.account: Read complete after 0s [........] module.securityhub_admin_global.data.aws_caller_identity.account: Read complete after 1s [........] module.securityhub_admin_global.data.aws_caller_identity.admin: Read complete after 1s [........] module.securityhub_admin_global.data.aws_organizations_organization.org: Read complete after 2s [........] Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: + create \u0026lt;= read (data resources) Terraform will perform the following actions: # ... Plan: 50 to add, 0 to change, 0 to destroy. Once we apply the configuration, after about 24 hours, we can see the score per security standard, and the findings which need to be addressed.\nConclusion # AWS Config and AWS Security Hub can play a big part in keeping your environment secure, and helping you to prepare for your PCI DSS assessment.\nOne thing to be aware of, is that both services have to be set up for every region you do business in. To limit the regions, you can use a Service Control Policy to limit the regions you can deploy to, while taking global services into account.\n","date":"9 November 2022","externalUrl":null,"permalink":"/2022/11/09/how-to-enable-and-configure-security-hub-for-pci-dss-compliancy-checks-using-terraform/","section":"Posts","summary":"How to enable and configure Security Hub for PCI DSS compliancy checks using Terraform # This article is also posted here\n","title":"How to enable and configure Security Hub for PCI DSS compliancy checks using Terraform","type":"posts"},{"content":"","date":"9 November 2022","externalUrl":null,"permalink":"/tags/pcs-dss/","section":"Tags","summary":"","title":"PCS DSS","type":"tags"},{"content":"","date":"9 November 2022","externalUrl":null,"permalink":"/tags/security-hub/","section":"Tags","summary":"","title":"Security Hub","type":"tags"},{"content":"","date":"9 November 2022","externalUrl":null,"permalink":"/tags/securityhub/","section":"Tags","summary":"","title":"Securityhub","type":"tags"},{"content":"","date":"1 August 2022","externalUrl":null,"permalink":"/tags/github/","section":"Tags","summary":"","title":"Github","type":"tags"},{"content":"When using Github Actions to deploy infrastructure to AWS with Terraform, you can use Open ID Connect (OIDC) to grant Github access to AWS without needing to provide access keys.\nAs a GitHub workflow, this would be somewhat like this:\nAnd as a diagram, it would look something like this:\nThis makes deploying a solution more complex, since the code needs to be broken up in parts per account (stacks). This requires a separate Terraform state file for each stack, and will (most likely) result in multiple stacks with dependencies on each other.\nOr, alternatively, we\u0026rsquo;ll be role chaining, which has some limitations, as well as administrative challenges.\nWhen using web identities, e.g. setting up OIDC in every accounts, we can can set conditions per role, per account.\nUsing \u0026lsquo;AssumeRoleWithWebIdentity\u0026rsquo; # The AWS API supports assuming roles using a web identity.\nWe can use this in Terraform to logically group resources in stacks, regardless of the accounts in which they need to be deployed, and without the need to create AssumeRole-policies in every account.\nTo use this method, we have to set up GitHub OIDC on each target account with a corresponding role and create a web token file in the workflow.\nOn the Terraform-side, we need to use at least version 4.22.0 of the AWS provider, since a bug which prevented the assume_role_with_web_identity to work properly has been fixed in that version.\nFor the S3 backend, we also need to create a credentials file, since the backend doesn\u0026rsquo;t (yet) support web identities.\nNOTE: To get this to work, no AWS-specific environment variables should be set in the workflow. If there are any, those can disrupt the process.\nDISCLAIMER: The examples given in this post do not necessarily show the use of best practices; they are given purely to show how the mechanics work.\nSetting up OIDC in an account # To set up OIDC, we have to add an identity provider, provide the Provider URL, get the thumbprint of the provider certificate,set an audience and assign a role. The procedure can be found on GitHub Docs.\nAlternatively, a CloudFormation template can be used to accomplish the same, as well as create a role and assign it to the identity provider to use it (thanks Aidan Steele):\nParameters: GitHubOwner: Type: String Description: Owner of the repository/repositories GitHubRepositoryFilter: Type: String Description: Filter to determine the repositories the role can be assumed from Default: \u0026#34;*\u0026#34; GitHubActionsRoleName: Type: String Description: Name for the GitHub Actions OIDC Role Default: GitHubActionsRole Resources: Role: Type: AWS::IAM::Role Properties: RoleName: !Sub ${GitHubActionsRoleName} ManagedPolicyArns: [arn:aws:iam::aws:policy/AdministratorAccess] AssumeRolePolicyDocument: Statement: - Effect: Allow Action: sts:AssumeRoleWithWebIdentity Principal: Federated: !Ref GitHubOidc Condition: StringLike: token.actions.githubusercontent.com:sub: !Sub repo:${GitHubOwner}/${GitHubRepositoryFilter}:* StringEquals: token.actions.githubusercontent.com:aud: \u0026#34;sts.amazonaws.com\u0026#34; GitHubOidc: Type: AWS::IAM::OIDCProvider Properties: Url: https://token.actions.githubusercontent.com ThumbprintList: [6938fd4d98bab03faadb97b34396831e3780aea1] ClientIdList: - \u0026#34;sts.amazonaws.com\u0026#34; Outputs: Role: Value: !GetAtt Role.Arn *NOTE: In this CloudFormation template the role is given full administrator access to the account. Best practice mandates that you use the principles of least privilege, especially in a production environment!\nOnce the OIDC connection has been set up for an account, and a role has been assigned to the connection, we can use a web token to connect to the account and assume the role.\nTo get the web token, we need to create a web identity token file.\nCreating a web identity token file # In the workflow, we add a step to create a web identity token file, as described on this GitHub Document (but adjusted for AWS):\n- name: Get OIDC Token id: get_oidc_token run: | curl -s -H \u0026#34;Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN\u0026#34; \u0026#34;$ACTIONS_ID_TOKEN_REQUEST_URL\u0026amp;audience=sts.amazonaws.com\u0026#34; | jq -r \u0026#39;.value\u0026#39; \u0026gt; /tmp/web_identity_token_file Using the web identity token file in Terraform # We can use the web identity token file directly in the AWS provider configuration in Terraform. This ensures that all changes will have to go through a GitHub Workflow.\nUsing a web identity for the S3 Backend # The Terraform S3 backend doesn\u0026rsquo;t currently support the use of a web identity directly. It does, however, support the use of a profile.\nAWS Documentation on how to configure profiles can be found here.\nSo we can define our backend as follows:\nterraform { backend \u0026#34;s3\u0026#34; { bucket = \u0026#34;terraform-state-bucket-111111111111-eu-west-1\u0026#34; key = \u0026#34;eu-west-1/terraform-webidentity/terraform.tfstate\u0026#34; region = \u0026#34;eu-west-1\u0026#34; encrypt = true dynamodb_table = \u0026#34;terraform-state-lock-table-111111111111\u0026#34; profile = \u0026#34;backend\u0026#34; shared_credentials_file = \u0026#34;~/.aws/credentials\u0026#34; } } With the corresponding profile entry in ~/.aws/credentials (we set up this file in the workflow later on in this write-up):\n[backend] region=eu-west-1 role_arn=arn:aws:iam::111111111111:role/GitHubActionsRole web_identity_token_file=/tmp/web_identity_token_file Configuring the AWS provider(s) # Terraform provides assume_role_with_web_identity in provider configurations, which we can use like this:\nprovider \u0026#34;aws\u0026#34; { # default profile set to \u0026#39;staging\u0026#39; account region = var.aws_region assume_role_with_web_identity { role_arn = \u0026#34;arn:aws:iam::222222222222:role/GitHubActionsRole\u0026#34; session_name = \u0026#34;github_action_session\u0026#34; web_identity_token_file = \u0026#34;/tmp/web_identity_token_file\u0026#34; } } provider \u0026#34;aws\u0026#34; { region = var.aws_region # Similar to the default provider, but this makes it easier to differentiate between providers in the code alias = \u0026#39;staging\u0026#39; assume_role_with_web_identity { role_arn = \u0026#34;arn:aws:iam::222222222222:role/GitHubActionsRole\u0026#34; session_name = \u0026#34;github_action_session\u0026#34; web_identity_token_file = \u0026#34;/tmp/web_identity_token_file\u0026#34; } } When you want to deploy to an additional account, we just have to add another provider with an assume_role_with_web_identity block and an alias. For example:\nprovider \u0026#34;aws\u0026#34; { region = var.aws_region alias = \u0026#34;production\u0026#34; assume_role_with_web_identity { role_arn = \u0026#34;arn:aws:iam::333333333333:role/GitHubActionsRole\u0026#34; session_name = \u0026#34;github_action_session\u0026#34; web_identity_token_file = \u0026#34;/tmp/web_identity_token_file\u0026#34; } } Using the provider(s) # Now we have configured the providers, we can start using them in our resource definitions.\nFor the sake of completeness, these are the files used in the project.\nbackend.tf # terraform { backend \u0026#34;s3\u0026#34; { bucket = \u0026#34;terraform-state-bucket-111111111111-eu-west-1\u0026#34; key = \u0026#34;eu-west-1/terraform-webidentity/terraform.tfstate\u0026#34; region = \u0026#34;eu-west-1\u0026#34; encrypt = true dynamodb_table = \u0026#34;terraform-state-lock-table-111111111111\u0026#34; profile = \u0026#34;backend\u0026#34; shared_credentials_file = \u0026#34;~/.aws/credentials\u0026#34; } } providers.tf # # We\u0026#39;re referring to variables for the account IDs and the GitHub role name to be used provider \u0026#34;aws\u0026#34; { # Default profile set to \u0026#39;staging\u0026#39; account region = var.aws_region assume_role_with_web_identity { role_arn = \u0026#34;arn:aws:iam::${var.aws_account_id_staging}:role/${var.github_role_name}\u0026#34; session_name = \u0026#34;github_action_session\u0026#34; web_identity_token_file = \u0026#34;/tmp/web_identity_token_file\u0026#34; } } provider \u0026#34;aws\u0026#34; { region = var.aws_region # Similar to the default provider, but this makes it easier to differentiate between providers in the code alias = \u0026#39;staging\u0026#39; assume_role_with_web_identity { role_arn = \u0026#34;arn:aws:iam::${var.aws_account_id_staging}:role/${var.github_role_name}\u0026#34; session_name = \u0026#34;github_action_session\u0026#34; web_identity_token_file = \u0026#34;/tmp/web_identity_token_file\u0026#34; } } provider \u0026#34;aws\u0026#34; { region = var.aws_region alias = \u0026#34;production\u0026#34; assume_role_with_web_identity { role_arn = \u0026#34;arn:aws:iam::${var.aws_account_id_production}:role/${var.github_role_name}\u0026#34; session_name = \u0026#34;github_action_session\u0026#34; web_identity_token_file = \u0026#34;/tmp/web_identity_token_file\u0026#34; } } datasources.tf # data \u0026#34;aws_availability_zones\u0026#34; \u0026#34;staging_available\u0026#34; { provider = aws.staging state = \u0026#34;available\u0026#34; } data \u0026#34;aws_vpc\u0026#34; \u0026#34;default_staging\u0026#34; { provider = aws.staging default = true } data \u0026#34;aws_availability_zones\u0026#34; \u0026#34;production_available\u0026#34; { provider = aws.production state = \u0026#34;available\u0026#34; } data \u0026#34;aws_vpc\u0026#34; \u0026#34;default_production\u0026#34; { provider = aws.production default = true } main.tf # resource \u0026#34;aws_subnet\u0026#34; \u0026#34;staging_private\u0026#34; { provider = aws.staging vpc_id = data.aws_vpc.default_staging.id availability_zone = data.aws_availability_zones.staging_available.names[0] cidr_block = cidrsubnet(data.aws_vpc.default_staging.cidr_block, 8, 1) tags = { Name = \u0026#34;staging-private-${data.aws_availability_zones.staging_available.names[0]}\u0026#34; } } resource \u0026#34;aws_subnet\u0026#34; \u0026#34;production_private\u0026#34; { provider = aws vpc_id = data.aws_vpc.default_production.id availability_zone = data.aws_availability_zones.production_available.names[0] cidr_block = cidrsubnet(data.aws_vpc.default_production.cidr_block, 8, 1) tags = { Name = \u0026#34;production-private-${data.aws_availability_zones.production_available.names[0]}\u0026#34; } } outputs.tf # output \u0026#34;staging_identity\u0026#34; { value = data.aws_caller_identity.staging } output \u0026#34;staging_private_subnet_cidr\u0026#34; { value = aws_subnet.staging_private.cidr_block } output \u0026#34;production_identity\u0026#34; { value = data.aws_caller_identity.production } output \u0026#34;production_private_subnet_cidr\u0026#34; { value = aws_subnet.production_private.cidr_block } variables.tf # variable \u0026#34;aws_account_id_production\u0026#34; { type = string description = \u0026#34;Account ID of the production account\u0026#34; } variable \u0026#34;aws_account_id_staging\u0026#34; { type = string description = \u0026#34;Account ID of the staging account\u0026#34; } variable \u0026#34;aws_region\u0026#34; { type = string description = \u0026#34;AWS region to use\u0026#34; } variable \u0026#34;github_role_name\u0026#34; { type = string description = \u0026#34;Name of the GitHub role to use\u0026#34; } variable \u0026#34;web_identity_token_file\u0026#34; { type = string default = \u0026#34;/tmp/web_identity_token_file\u0026#34; } versions.tf (not required but best practice) # terraform { required_version = \u0026#34;\u0026gt;= 1.2.0, \u0026lt; 1.3.0\u0026#34; required_providers { aws = { source = \u0026#34;hashicorp/aws\u0026#34; version = \u0026#34;4.22.0\u0026#34; } } } terraform.tfvars (not best practice to use a name that is automatically used, but for the sake of simplicity) # aws_account_id_staging = \u0026#34;222222222222\u0026#34; aws_account_id_production = \u0026#34;333333333333\u0026#34; aws_region = \u0026#34;eu-west-1\u0026#34; github_role_name = \u0026#34;GitHubActionsRole\u0026#34; web_identity_token_file = \u0026#34;/tmp/web_identity_token_file\u0026#34; credentials.aws (will be copied to the default location in the workflow) # [backend] region=eu-west-1 role_arn=arn:aws:iam::000000000000:role/GitHubActionsRole web_identity_token_file=/tmp/web_identity_token_file NOTE: The two blank lines at the end are intentional and need to be there. When there\u0026rsquo;s less blank lines, authentication can go wrong and throw an error.\nThe GitHub Workflow # To use all of this in a GitHub Workflow, we need to define the workflow.\nThe following is an example definition of such a workflow:\n/.github/workflows/terraform.yml # name: \u0026#34;Terraform Actions - WebIdentity\u0026#34; on: workflow_dispatch: inputs: terraform_version: description: \u0026#34;Version of Terraform to use\u0026#34; required: true default: \u0026#34;1.2.2\u0026#34; # \u0026#39;latest\u0026#39; type: string push: branches: - main env: WEB_IDENTITY_TOKEN_FILE: /tmp/web_identity_token_file PLAN_FILE: terraform.plan jobs: terraform: name: \u0026#34;Terraform Apply Actions\u0026#34; runs-on: ubuntu-latest permissions: id-token: write contents: read # This is required for actions/checkout steps: - name: Checkout uses: actions/checkout@v3 - name: Get OIDC Token id: aws_sts_creds run: | curl -s -H \u0026#34;Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN\u0026#34; \u0026#34;$ACTIONS_ID_TOKEN_REQUEST_URL\u0026amp;audience=sts.amazonaws.com\u0026#34; | jq -r \u0026#39;.value\u0026#39; \u0026gt; $WEB_IDENTITY_TOKEN_FILE - name: Setup AWS credentials file run: | mkdir -p ~/.aws cp credentials.aws ~/.aws/credentials touch ~/.aws/config - name: Check current AWS configuration run: | aws sts get-caller-identity || echo \u0026#34;[OK] - No AWS credentials found!\u0026#34; (env | grep AWS_) || echo \u0026#34;[OK] - No AWS environment variables present!\u0026#34; continue-on-error: true - name: Setup Terraform uses: hashicorp/setup-terraform@v2 with: terraform_version: ${{ github.event.inputs.terraform_version }} - name: \u0026#34;Terraform Init\u0026#34; id: init run: terraform init - name: \u0026#34;Terraform Validate\u0026#34; id: validate run: terraform validate - name: \u0026#34;Terraform Plan\u0026#34; id: plan run: terraform plan -out=$PLAN_FILE - name: \u0026#34;Terraform Apply\u0026#34; id: apply run: terraform apply $PLAN_FILE Using profiles with the web identity token file # When using the web identity token file directly in Terraform, you cannot use the same code and execute Terraform-commands both locally and in a GitHub Workflow, because we do not have a GitHub OIDC token locally.\nTo still be able to run commands locally, and use the web identity token file, we can use profiles, as we already do for the backend.\nWhen configuring profiles, the actual configuration between what you use locally and what\u0026rsquo;s used in the workflow can be different, as long as the names of the profiles used are the same.\nDo note, however, that this introduces a difference between local and workflow deployments, since local profiles might have different privileges set.\nA setup using profiles, requires changes to the previous configuration.\nChanges to Terraform files # credentials.aws (will be copied to the default location in the workflow) # We add the profiles for the other accounts here as well.\nIn this example the account IDs and role name(s) are \u0026lsquo;hardcoded\u0026rsquo;. These could be made variable when using an extra step to produce the credentials file, which builds the file dynamically. This is beyond the scope of this post, though.\n[backend] region=eu-west-1 role_arn=arn:aws:iam::000000000000:role/GitHubActionsRole web_identity_token_file=/tmp/web_identity_token_file [staging] region=eu-west-1 role_arn=arn:aws:iam::111111111111:role/GitHubActionsRole web_identity_token_file=/tmp/web_identity_token_file [production] region=eu-west-1 role_arn=arn:aws:iam::222222222222:role/GitHubActionsRole web_identity_token_file=/tmp/web_identity_token_file NOTE: The two blank lines at the end are intentional and need to be there. When there\u0026rsquo;s less blank lines, authentication can go wrong and throw an error.\nproviders.tf # Here we need to reference the profiles we\u0026rsquo;re using, instead of the assume_role_with_web_identity blocks which reference the web identity token file.\nprovider \u0026#34;aws\u0026#34; { region = var.aws_region # Default profile set to \u0026#39;staging\u0026#39; account profile = \u0026#34;staging\u0026#34; } provider \u0026#34;aws\u0026#34; { region = var.aws_region # Similar to the default provider, but this makes it easier to differentiate between providers in the code alias = \u0026#34;staging\u0026#34; profile = \u0026#34;staging\u0026#34; } provider \u0026#34;aws\u0026#34; { region = var.aws_region alias = \u0026#34;production\u0026#34; profile = \u0026#34;production\u0026#34; } variables.tf # Here we can leave out the variables for the account IDs.\nvariable \u0026#34;aws_region\u0026#34; { type = string description = \u0026#34;AWS region to use\u0026#34; } variable \u0026#34;github_role_name\u0026#34; { type = string description = \u0026#34;Name of the GitHub role to use\u0026#34; } variable \u0026#34;web_identity_token_file\u0026#34; { type = string default = \u0026#34;/tmp/web_identity_token_file\u0026#34; } terraform.tfvars (not best practice to use a name that is automatically used, but for the sake of simplicity) # Here we can leave out the variables for the account IDs.\naws_region = \u0026#34;eu-west-1\u0026#34; github_role_name = \u0026#34;GitHubActionsRole\u0026#34; web_identity_token_file = \u0026#34;/tmp/web_identity_token_file\u0026#34; Changes to the GitHub Workflow # All changes are done within the Terraform code. The only change that might be needed in the workflow, is if the credentials file were to be created dynamically.\nSince we\u0026rsquo;re not doing that for this post, no changes to the workflow are needed.\nCaveats # No AWS environment variables can be set in the workflow; these will override the use of the web identity token file A credentials file needs to be created in the workflow; this could be done dynamically in the same workflow with, for example, Terraform, or another language Currently the Terraform S3 backend doesn\u0026rsquo;t support web identities directly, but requires a profile ","date":"1 August 2022","externalUrl":null,"permalink":"/2022/08/01/how-to-use-github-oidc-and-terraform-to-assume-roles-in-aws-using-webidentity/","section":"Posts","summary":"When using Github Actions to deploy infrastructure to AWS with Terraform, you can use Open ID Connect (OIDC) to grant Github access to AWS without needing to provide access keys.\nAs a GitHub workflow, this would be somewhat like this:\n","title":"How to use Github OIDC and Terraform to assume roles in AWS using WebIdentity","type":"posts"},{"content":"","date":"1 August 2022","externalUrl":null,"permalink":"/tags/webidentity/","section":"Tags","summary":"","title":"Webidentity","type":"tags"},{"content":"","date":"13 June 2022","externalUrl":null,"permalink":"/posts/","section":"Posts","summary":"","title":"Posts","type":"posts"},{"content":"","date":"26 August 2019","externalUrl":null,"permalink":"/tags/javascript/","section":"Tags","summary":"","title":"Javascript","type":"tags"},{"content":"I\u0026rsquo;ve been tinkering on a Discord-bot in Javascript using Node.js, together with my best friend.\nFor easier testing, we run the bot in Docker, so we don\u0026rsquo;t have all the dependencies littering our computers. We do want to be able to debug the bot, though, which is where I ran into an article on the internet by oneturkmen. The article has been deleted, but the GitHub-repository is (at the time of writing this article) still available here.\nI\u0026rsquo;m running Windows 10 Professional, with Docker installed using the \u0026lsquo;Containers\u0026rsquo;-feature of Windows 10.\nFirst, we have to have a docker-compose.yml file, with the following data:\n#docker-compose run --rm node /bin/bash node: image: node:carbon working_dir: /usr/src/app volumes: - .:/usr/src/app environment: - DISCORD_TOKEN ports: - \u0026#34;9229:9229\u0026#34; command: node -r dotenv/config --inspect=0.0.0.0 app.js ","date":"26 August 2019","externalUrl":null,"permalink":"/2019/08/26/vs-code-and-javascript-debugging/","section":"Posts","summary":"I’ve been tinkering on a Discord-bot in Javascript using Node.js, together with my best friend.\nFor easier testing, we run the bot in Docker, so we don’t have all the dependencies littering our computers. We do want to be able to debug the bot, though, which is where I ran into an article on the internet by oneturkmen. The article has been deleted, but the GitHub-repository is (at the time of writing this article) still available here.\n","title":"VS Code and Javascript Debugging","type":"posts"},{"content":"","date":"26 August 2019","externalUrl":null,"permalink":"/tags/vscode/","section":"Tags","summary":"","title":"Vscode","type":"tags"},{"content":"It\u0026rsquo;s been a while (again) since I posted to this blog/website. The last 8 months I\u0026rsquo;ve been at home on sick-leave because I burned out.\nThe road has been - and still is - long, since\u0026hellip;\n","date":"18 July 2019","externalUrl":null,"permalink":"/2019/07/18/personal-note/","section":"Posts","summary":"It’s been a while (again) since I posted to this blog/website. The last 8 months I’ve been at home on sick-leave because I burned out.\nThe road has been - and still is - long, since…\n","title":"Personal Note","type":"posts"},{"content":"","date":"7 March 2018","externalUrl":null,"permalink":"/tags/azure/","section":"Tags","summary":"","title":"Azure","type":"tags"},{"content":"","date":"7 March 2018","externalUrl":null,"permalink":"/tags/azure-ad/","section":"Tags","summary":"","title":"Azure AD","type":"tags"},{"content":"We\u0026rsquo;ve had an issue where the password of an automation account we use had expired.\nSince we don\u0026rsquo;t want to have to change password of these kinds of accounts, we set the password policy to not expire the password.\nThis was done using the following PowerShell-code:\n$SubscriptionName = \u0026#34;Azure Subscription\u0026#34; $AutomationAccountName = \u0026#34;AzureAutomationAccount\u0026#34; Install-Module AzureAD $_subscription = Login-AzureRmAccount -SubscriptionName $SubscriptionName $_subscription \u0026lt;# OUTPUT: Environment : AzureCloud Account : username@domain.com TenantId : 9add9526-e870-4229-a0cd-87ac588d2687 SubscriptionId : 510df0e4-1f7f-47fb-9171-a1639cdf79a0 SubscriptionName : Azure Subscription CurrentStorageAccount : #\u0026gt; # Conenct to the AD of the subscription Connect-AzureAD -TenantId $_subscription.TenantId \u0026lt;# OUTPUT: Account Environment TenantId TenantDomain AccountType ------- ----------- -------- ------------ ----------- username@domain.com AzureCloud 9add9526-e870-4229-a0cd-87ac588d2687 directory.onmicrosoft.com User #\u0026gt; # Check the current passwordpolicy (Get-AzureADUser -SearchString $AutomationAccountName).PasswordPolicies # returns null # Set the password to never expire Get-AzureADUser -SearchString $AutomationAccountName | Set-AzureAdUser -PasswordPolicies DisablePasswordExpiration # Check the passwordpolicy again (Get-AzureADUser -SearchString $AutomationAccountName).PasswordPolicies # returns non-expiring password set # OUTPUT: # DisablePasswordExpiration ","date":"7 March 2018","externalUrl":null,"permalink":"/2018/03/07/azure-expiring-passwords-of-automation-accounts/","section":"Posts","summary":"We’ve had an issue where the password of an automation account we use had expired.\nSince we don’t want to have to change password of these kinds of accounts, we set the password policy to not expire the password.\n","title":"Azure Expiring Passwords of Automation Accounts","type":"posts"},{"content":"","date":"21 February 2018","externalUrl":null,"permalink":"/tags/meltdown/","section":"Tags","summary":"","title":"Meltdown","type":"tags"},{"content":"","date":"21 February 2018","externalUrl":null,"permalink":"/tags/registry/","section":"Tags","summary":"","title":"Registry","type":"tags"},{"content":"","date":"21 February 2018","externalUrl":null,"permalink":"/tags/spectre/","section":"Tags","summary":"","title":"Spectre","type":"tags"},{"content":"Recently we had to make some fixes to registry settings because of the Spectre/Meltdown patches for Windows. By default not all registry settings would be properly set, so that patches might not be activated.\nIn order to fix this, I wrote a little script to easily set the registry keys.\nWe created an EXE-file from this script using PS2EXE-GUI and are now able to roll it out with our patching software and letting it report back the status using the exit code.\nHere\u0026rsquo;s an altered version of the script (originally we defined variables for each key, name and value and wrote an if-statement for each key/name/value-set):\n$_regSettings = @( # Reference: https://support.microsoft.com/en-us/help/4072699/january-3-2018-windows-security-updates-and-antivirus-software [pscustomobject]@{ Key = \u0026#39;HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\QualityCompat\u0026#39;; Name = \u0026#39;cadca5fe-87d3-4b96-b7fb-a231484277cc\u0026#39;; Value = \u0026#39;0\u0026#39;; Type = \u0026#39;DWORD\u0026#39;; FlagValue = 1 } # Reference: https://support.microsoft.com/en-hk/help/4073119/protect-against-speculative-execution-side-channel-vulnerabilities-in ,[pscustomobject]@{ Key = \u0026#39;HKLM:\\SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Memory Management\u0026#39;; Name = \u0026#39;FeatureSettingsOverride\u0026#39;; Value = \u0026#39;0\u0026#39;; Type = \u0026#39;DWORD\u0026#39;; FlagValue = 2 } ,[pscustomobject]@{ Key = \u0026#39;HKLM:\\SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Memory Management\u0026#39;; Name = \u0026#39;FeatureSettingsOverrideMask\u0026#39;; Value = \u0026#39;3\u0026#39;; Type = \u0026#39;DWORD\u0026#39;; FlagValue = 4 } # Reference: https://support.microsoft.com/en-us/help/4072698/windows-server-guidance-to-protect-against-the-speculative-execution # Only for Hyper-V Hosts #,[pscustomobject]@{ Key = \u0026#39;SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Virtualization\u0026#39;; Name = \u0026#39;MinVmVersionForCpuBasedMitigations\u0026#39;; Value = \u0026#39;1.0\u0026#39;; Type = \u0026#39;REG_SZ\u0026#39;; FlagValue = 8 } ) $_exitCode = 0 foreach ($_regSetting in $_regSettings) { switch ($_regSetting.Type) { # String. Specifies a null-terminated string. Equivalent to REG_SZ. \u0026#34;String\u0026#34; { $_valueType = \u0026#34;String\u0026#34;; break; } \u0026#34;REG_SZ\u0026#34; { $_valueType = \u0026#34;String\u0026#34;; break; } # ExpandString. Specifies a null-terminated string that contains unexpanded references to environment variables that are expanded when the value is retrieved. Equivalent to REG_EXPAND_SZ. \u0026#34;ExpandString\u0026#34; { $_valueType = \u0026#34;String\u0026#34; } \u0026#34;REG_EXPAND_SZ\u0026#34; { $_valueType = \u0026#34;String\u0026#34; } # Binary. Specifies binary data in any form. Equivalent to REG_BINARY. \u0026#34;Binary\u0026#34; { $_valueType = \u0026#34;Byte[]\u0026#34;; break; } \u0026#34;REG_BINARY\u0026#34; { $_valueType = \u0026#34;Byte[]\u0026#34;; break; } # DWord. Specifies a 32-bit binary number. Equivalent to REG_DWORD. \u0026#34;DWORD\u0026#34; { $_valueType = \u0026#34;Int32\u0026#34;; break; } \u0026#34;REG_DWORD\u0026#34; { $_valueType = \u0026#34;Int32\u0026#34;; break; } # MultiString. Specifies an array of null-terminated strings terminated by two null characters. Equivalent to REG_MULTI_SZ. \u0026#34;MultiString\u0026#34; { $_valueType = \u0026#34;String[]\u0026#34;; break; } \u0026#34;REG_MULTI_SZ\u0026#34; { $_valueType = \u0026#34;String[]\u0026#34;; break; } # Qword. Specifies a 64-bit binary number. Equivalent to REG_QWORD. \u0026#34;QWORD\u0026#34; { $_valueType = \u0026#34;Int64\u0026#34;; break; } \u0026#34;REG_QWORD\u0026#34; { $_valueType = \u0026#34;Int64\u0026#34;; break; } # Unknown. Indicates an unsupported registry data type, such as REG_RESOURCE_LIST default {throw \u0026#34;Unknown type\u0026#34; } } if ((Get-ItemProperty -Path $_regSetting.Key -Name $_regSetting.Name).($_regSetting.Name) -ne $_regSetting.Value -or (Get-ItemProperty -Path $_regSetting.Key -Name $_regSetting.Name).($_regSetting.Name).GetType().Name -ne $_valueType) { try { Write-Verbose -Verbose \u0026#34;Registrysetting not found or not properly set: \u0026#39;$($_regSetting.Key)\\$($_regSetting.Name)\u0026#39;\u0026#34; if (!(Test-Path $_regSetting.Key)) { New-Item -Path $_regSetting.Key } New-ItemProperty -Path $_regSetting.Key -Name $_regSetting.Name -Value $_regSetting.Value -Force -PropertyType DWORD -ErrorAction Stop } catch { Write-Error \u0026#34;Unable to set registry: \u0026#39;$($_regSetting.Key)\\$($_regSetting.Name)\u0026#39;\u0026#34; $_exitCode = $_exitCode + $_regSetting.FlagValue } } } \u0026lt;# $_exitCode 0 : No missing or misconfigured registry keys $_exitCode 1 : Unable to set QualityCompat $_exitCode 2 : Unable to set FeatureSettingsOverride $_exitCode 3 : Unable to set QualityCompat and FeatureSettingsOverride $_exitCode 4 : Unable to set FeatureSettingsOverrideMask $_exitCode 5 : Unable to set QualityCompat and FeatureSettingsOverrideMask $_exitCode 6 : Unable to set FeatureSettingsOverride and FeatureSettingsOverrideMask $_exitCode 7 : Unable to set QualityCompat, FeatureSettingsOverride and FeatureSettingsOverrideMask $_exitCode 8 : Unable to set MinVmVersionForCpuBasedMitigations $_exitCode 9 : Unable to set QualityCompat and MinVmVersionForCpuBasedMitigations $_exitCode 10 : Unable to set FeatureSettingsOverride and MinVmVersionForCpuBasedMitigations $_exitCode 11 : Unable to set QualityCompat, FeatureSettingsOverride and MinVmVersionForCpuBasedMitigations $_exitCode 12 : Unable to set FeatureSettingsOverrideMask and MinVmVersionForCpuBasedMitigations $_exitCode 13 : Unable to set QualityCompat, FeatureSettingsOverrideMask and MinVmVersionForCpuBasedMitigations $_exitCode 14 : Unable to set FeatureSettingsOverride, FeatureSettingsOverrideMask and MinVmVersionForCpuBasedMitigations $_exitCode 15 : Unable to set QualityCompat, FeatureSettingsOverride, FeatureSettingsOverrideMask and MinVmVersionForCpuBasedMitigations #\u0026gt; Write-Verbose -Verbose \u0026#34;Exit-code : $_exitCode\u0026#34; exit($_exitCode) ","date":"21 February 2018","externalUrl":null,"permalink":"/2018/02/21/spectre/meltdown-registry-keys/","section":"Posts","summary":"Recently we had to make some fixes to registry settings because of the Spectre/Meltdown patches for Windows. By default not all registry settings would be properly set, so that patches might not be activated.\n","title":"Spectre/Meltdown Registry Keys","type":"posts"},{"content":"","date":"21 February 2018","externalUrl":null,"permalink":"/tags/windows/","section":"Tags","summary":"","title":"Windows","type":"tags"},{"content":"","date":"9 September 2017","externalUrl":null,"permalink":"/tags/btrfs/","section":"Tags","summary":"","title":"Btrfs","type":"tags"},{"content":"","date":"9 September 2017","externalUrl":null,"permalink":"/tags/synology/","section":"Tags","summary":"","title":"Synology","type":"tags"},{"content":"A couple of months ago. I got myself a new Synology to replace my older DS213. The choice was made for a DS716+II, which also supports Btrfs. To migrate from the DS213 to the DS716, I took out one of the mirror-drives of the DS213 and placed it in the DS716 to reformat and then gradually transfer the data to the new Syno. Once everything would be transferred, I\u0026rsquo;d put the remaining disk of the DS213 and add it to the DS716.\nAfter adding and wiping the second drive to the DS716, my Btrfs-volume decided to crash\u0026hellip;\nSince the second disk was already wiped, I had no other backup of my data, so I began searching the internet. I did notice, when logging in to the NAS with SSH, that my data was still present, but the volume wouldn\u0026rsquo;t come back online.\nAfter several searches, I came across this post from Sébastien Dubois, where he encountered the same error.\nWhen checking the health of the volume, I had the following output:\n\u0026gt; mdadm --detail /dev/md2 /dev/md2: Version : 1.2 Creation Time : Thu Apr 27 02:33:31 2017 Raid Level : raid1 Array Size : 2925444544 (2789.92 GiB 2995.66 GB) Used Dev Size : 2925444544 (2789.92 GiB 2995.66 GB) Raid Devices : 2 Total Devices : 1 Persistence : Superblock is persistent Update Time : Sun Apr 30 12:25:01 2017 State : clean, degraded Active Devices : 1 Working Devices : 1 Failed Devices : 0 Spare Devices : 0 Name : ds716:2 (local to host ds716) UUID : 5d2c337f:99f93c69:ac00d5b7:f5c6819f Events : 9685 Number Major Minor RaidDevice State 0 8 3 0 active sync /dev/sda3 1 0 0 1 removed Inspection of the disk gave me the following output:\n\u0026gt; mdadm --examine /dev/sda3 /dev/sda3: Magic : a92b4efc Version : 1.2 Feature Map : 0x0 Array UUID : 5d2c337f:99f93c69:ac00d5b7:f5c6819f Name : ds716:2 (local to host ds716) Creation Time : Thu Apr 27 02:33:31 2017 Raid Level : raid1 Raid Devices : 2 Avail Dev Size : 5850889120 (2789.92 GiB 2995.66 GB) Array Size : 5850889088 (2789.92 GiB 2995.66 GB) Used Dev Size : 5850889088 (2789.92 GiB 2995.66 GB) Data Offset : 2048 sectors Super Offset : 8 sectors State : clean Device UUID : de0fb6cf:a3f04514:93f8ac77:ef9a8dec Update Time : Sun Apr 30 12:25:01 2017 Checksum : cd541bd3 - correct Events : 9685 Device Role : Active device 0 Array State : A. (\u0026#39;A\u0026#39; == active, \u0026#39;.\u0026#39; == missing) Sébastien had contacted Synology about his issue, and got remote help. Using the commands he retrieved from the log-file, I executed the following commands to re-create the array to get it back up again.\n# Stop all NAS services except from SSH syno_poweroff_task -d # Stop the RAID-set mdadm --stop /dev/md2 # Unmount a volume umount /volume1 #Recreate the RAID-set mdadm -Cf /dev/md2 -e1.2 -n1 -l1 /dev/sda3 -u5d2c337f:99f93c69:ac00d5b7:f5c6819f The actual output of the last command was the following:\n\u0026gt; mdadm -Cf /dev/md2 -e1.2 -n1 -l1 /dev/sda3 -u5d2c337f:99f93c69:ac00d5b7:f5c6819f mdadm: /dev/sda3 appears to be part of a raid array: level=raid1 devices=2 ctime=Thu Apr 27 02:33:31 2017 Continue creating array? y mdadm: array /dev/md2 started. \u0026gt; mdadm --detail /dev/md2 /dev/md2: Version : 1.2 Creation Time : Sun Apr 30 12:44:12 2017 Raid Level : raid1 Array Size : 2925444544 (2789.92 GiB 2995.66 GB) Used Dev Size : 2925444544 (2789.92 GiB 2995.66 GB) Raid Devices : 1 Total Devices : 1 Persistence : Superblock is persistent Update Time : Sun Apr 30 12:44:12 2017 State : clean Active Devices : 1 Working Devices : 1 Failed Devices : 0 Spare Devices : 0 Name : ds716:2 (local to host ds716) UUID : 5d2c337f:99f93c69:ac00d5b7:f5c6819f Events : 0 Number Major Minor RaidDevice State 0 8 3 0 active sync /dev/sda3 After that, the Btrfs-volume was back up again, as confirmed by mdstat.\n\u0026gt; cat /proc/mdstat Personalities : [linear] [raid0] [raid1] [raid10] [raid6] [raid5] [raid4] md2 : active raid1 sda3[0] 2925444544 blocks super 1.2 [1/1] [U] md3 : active raid1 sdb3[0] 2925444544 blocks super 1.2 [1/1] [U] md1 : active raid1 sdb2[1] sda2[0] 2097088 blocks [2/2] [UU] md0 : active raid1 sdb1[1] 2490176 blocks [2/1] [_U] unused devices: \u0026lt;none\u0026gt; The issue I had, occured late on a Friday. I did submit a support-ticket with Synology, but since it already was weekend, I wasn\u0026rsquo;t expecting to hear back from them until Monday.\nDuring the troubleshooting I did, I kept them updated. On Monday I got a request to send them a diagnostics pacakge.\nI appeared one of my drives was having a crazy amount of bad blocks and they urged me to replace that drive as soon as possible.\nThe faulty drive was a WD Red, which was still under warranty. Luckily for me, it got replaced and everything is working as intended now.\n","date":"9 September 2017","externalUrl":null,"permalink":"/2017/09/09/synology-btrfs-crash/","section":"Posts","summary":"A couple of months ago. I got myself a new Synology to replace my older DS213. The choice was made for a DS716+II, which also supports Btrfs. To migrate from the DS213 to the DS716, I took out one of the mirror-drives of the DS213 and placed it in the DS716 to reformat and then gradually transfer the data to the new Syno. Once everything would be transferred, I’d put the remaining disk of the DS213 and add it to the DS716.\n","title":"Synology Btrfs crash","type":"posts"},{"content":"Recently I\u0026rsquo;ve switched from Wordpress to Github Pages. I\u0026rsquo;m using Jekyll with Github pages, which means I get to write my posts in Markdown. Since I didn\u0026rsquo;t want my old posts to go to waste entirely, I decided to see how I could \u0026rsquo;extract\u0026rsquo; the posts from the Wordpress export I had, and put them in seperate Markdown-files.\nI came up with the following script:\n$_postPath = \u0026#39;.\\_posts\\\u0026#39; $_oldWordPress = [xml](Get-Content \u0026#34;~\\wordpress-backup.xml\u0026#34;) foreach ($_item in $_oldWordPress.rss.channel.item) { $_postLayout = $_item.post_type $_postTitle = $_item.title if ($_postTitle -eq $null -or $_postTitle -like [string]::Empty) { $_postTitle = $_item.post_name } # Construct filename of the converted post, make sure anything other than letters and numbers is replaced by a dash. $_postFileName = \u0026#34;$(([datetime]::Parse($_item.post_date)).ToString(\u0026#39;yyyy-MM-dd\u0026#39;))-$($_postTitle)\u0026#34; -replace \u0026#39;[^a-zA-Z0-9]\u0026#39;,\u0026#39;-\u0026#39; # $_postFileName # We have to use a here-string to build up the markdown-file. @\u0026#34; --- layout: $($_postLayout) title: $($_postTitle) --- $($_item.encoded.\u0026#39;#cdata-section\u0026#39; -replace \u0026#34;\u0026lt;[/]?code\u0026gt;\u0026#34;,\u0026#34;``````\u0026#34; -replace \u0026#34;\u0026lt;!--more--\u0026gt;\u0026#34;,\u0026#34;\u0026#34; -replace \u0026#34;\u0026lt;[/]?strong\u0026gt;\u0026#34;,\u0026#34;**\u0026#34; -replace \u0026#34;\u0026lt;[/]?em\u0026gt;\u0026#34;,\u0026#34;_\u0026#34; -replace \u0026#39;\u0026amp;gt;\u0026#39;,\u0026#39;\u0026gt;\u0026#39; -replace \u0026#39;\u0026amp;lt;\u0026#39;,\u0026#39;\u0026lt;\u0026#39;) \u0026#34;@ | Set-Content (Join-Path $_postPath \u0026#34;$($_postFileName).md\u0026#34;) } There\u0026rsquo;s room for improvement, but I thought it\u0026rsquo;s a nice start, at least :)\n","date":"16 January 2017","externalUrl":null,"permalink":"/2017/01/16/converting-wordpress-blog-to-markdown/","section":"Posts","summary":"Recently I’ve switched from Wordpress to Github Pages. I’m using Jekyll with Github pages, which means I get to write my posts in Markdown. Since I didn’t want my old posts to go to waste entirely, I decided to see how I could ’extract’ the posts from the Wordpress export I had, and put them in seperate Markdown-files.\n","title":"Converting Wordpress-blog to Markdown","type":"posts"},{"content":"","date":"16 January 2017","externalUrl":null,"permalink":"/tags/markdown/","section":"Tags","summary":"","title":"Markdown","type":"tags"},{"content":"","date":"16 January 2017","externalUrl":null,"permalink":"/tags/powershell/","section":"Tags","summary":"","title":"Powershell","type":"tags"},{"content":"","date":"16 January 2017","externalUrl":null,"permalink":"/tags/wordpress/","section":"Tags","summary":"","title":"Wordpress","type":"tags"},{"content":"I\u0026rsquo;ve been troubleshooting an issue where meeting requests are being received by mailboxes/users that are not included in the requests. To get a list of calendar delegates for mailboxes where these are set, I found the following couple of lines quite handy:\n$output = [System.Collections.ArrayList]@() foreach ($_mbx in (Get-Mailbox -ResultSize Unlimited | Get-CalendarProcessing | Where-Object { $_.ResourceDelegates -notlike [string]::Empty })) { $_mbx.ResourceDelegates | %{ $output.Add(([PSCustomObject]@{ User = $_mbx.Identity; Delegate = $_.ToString() })) | Out-Null } } $output ","date":"29 September 2016","externalUrl":null,"permalink":"/2016/09/29/exchange-listing-calendar-delegates-for-mailboxes/","section":"Posts","summary":"I’ve been troubleshooting an issue where meeting requests are being received by mailboxes/users that are not included in the requests. To get a list of calendar delegates for mailboxes where these are set, I found the following couple of lines quite handy:\n","title":"Exchange - Listing Calendar-delegates for mailboxes","type":"posts"},{"content":"I\u0026rsquo;ve been working with Exchange some more, lately, where I\u0026rsquo;ve also had to generate an overview of the mailboxes, mailbox-types and if the underlying account is enabled or not. This had to be exported to a CSV-file. I came to the next lines of code for that:\n$AllMailboxes = Get-Mailbox $AllMailboxes | select Name,PrimarySmtpAddress,DistinguishedName,OrganizationalUnit,RecipientTypeDetails,@{Name=\u0026#34;AccountEnabled\u0026#34;;Expression={(Get-ADUser $_.DistinguishedName).Enabled}} | Export-Csv \u0026#34;~\\Documents\\AllMailboxes_$((Get-Date).ToString(\u0026#34;yyyyMMdd_HHmm\u0026#34;)).csv\u0026#34; -Delimiter \u0026#34;;\u0026#34; -NoTypeInformation ","date":"29 September 2016","externalUrl":null,"permalink":"/2016/09/29/exchange-listing-mailboxes-mailbox-type-and-if-the-account-is-enabled/","section":"Posts","summary":"I’ve been working with Exchange some more, lately, where I’ve also had to generate an overview of the mailboxes, mailbox-types and if the underlying account is enabled or not. This had to be exported to a CSV-file. I came to the next lines of code for that:\n","title":"Exchange - Listing mailboxes, mailbox-type and if the account is enabled","type":"posts"},{"content":"Sometimes it happens that the network profile of a connection in Windows is incorrectly categorised. Sometimes such an event happens out of the blue, even. This can be fixed by changing the value of the value of the Category-key of the network profile in the registry, which can be found here:\nHKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\NetworkList\\Profiles\\ There\u0026rsquo;s a sub-key for each network listed.\nThe following script can be used to change the category of the active network connection:\n#Requires -Version 3.0 # Set-NetworkConnectionCategory.ps1 [CmdletBinding()] Param( [Parameter(Mandatory=$true, HelpMessage=\u0026#39;The category to set the network-connection to (Public, Private or Domain)\u0026#39;)] [ValidateSet(\u0026#39;Public\u0026#39;,\u0026#39;Private\u0026#39;,\u0026#39;Domain\u0026#39;)] $ConnectionCategory ) Add-Type -Language CSharp -TypeDefinition @\u0026#34; public enum ConnectionCategory{ Public, Private, Domain } \u0026#34;@ $ConnectedNetwork = (Get-NetConnectionProfile).Name $NetworkConnectionProfiles = Get-ChildItem \u0026#39;HKLM:\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\NetworkList\\Profiles\\\u0026#39; | select -expand Name $NetworkConnectionProfiles | Foreach-Object { $key = $_ -replace \u0026#34;^HKEY_LOCAL_MACHINE\u0026#34;, \u0026#34;HKLM:\u0026#34; # $keyToChange = (Get-ItemProperty $key).PSObject.Properties | Where-Object { $_.Value -match $ConnectedNetwork } # # $keyToChange | ForEach-Object { Set-ItemProperty -Path $key -Name $_.Name -Value ($_.Value -replace $pathToReplace,$newPath) -Verbose } if ((Get-ItemProperty $key).PSObject.Properties[\u0026#34;ProfileName\u0026#34;].Value -eq $ConnectedNetwork) { Write-Output \u0026#34;You are about to set the connectioncategory for \u0026#39;$ConnectedNetwork\u0026#39; to \u0026#39;$ConnectionCategory\u0026#39;.\u0026#34; Set-ItemProperty -Path $key -Name \u0026#34;Category\u0026#34; -Value ([int]([ConnectionCategory]::$ConnectionCategory)) -Verbose -Confirm:$true break; } } UPDATE: Or, you could just use:\nSet-NetConnectionProfile -NetworkCategory [DomainAuthenticated | Private | Public] \u0026hellip;\n","date":"11 August 2016","externalUrl":null,"permalink":"/2016/08/11/changing-the-network-profile-in-windows/","section":"Posts","summary":"Sometimes it happens that the network profile of a connection in Windows is incorrectly categorised. Sometimes such an event happens out of the blue, even. This can be fixed by changing the value of the value of the Category-key of the network profile in the registry, which can be found here:\n","title":"Changing the network profile in Windows","type":"posts"},{"content":"I\u0026rsquo;ve previously rooted my Desire HD, but reverted it back to stock a little while ago, after HTC announced they were NOT going to release an Android 4.x update for the DHD.\nI tried fre3vo repeatedly, but since I was using a newer Sense-version, I had to use a different method, which is described here.\nAfter that, gaining permroot using Visionary, installing ClockWorkMod and flashing a custom ROM were a piece of cake.\n","date":"25 August 2012","externalUrl":null,"permalink":"/2012/08/25/rooting-the-desire-hd/","section":"Posts","summary":"I’ve previously rooted my Desire HD, but reverted it back to stock a little while ago, after HTC announced they were NOT going to release an Android 4.x update for the DHD.\n","title":"Rooting the Desire HD","type":"posts"},{"content":"Today my friend and I have been working on installing Windows 7 on a MacBook Pro. Optical drive has been replaced by an SSD drive, and while trying to install Windows, we found out it will not boot from a USB device\u0026hellip;\nLuckily, the next webpage came to the rescue: http://huguesval.com/blog/2012/02/installing-windows-7-on-a-mac-without-superdrive-with-virtualbox/\nSince the MBP is running OS X Lion, we had to use WinClone 2.3.3, the last freeware-version of the tool.\nLuckily, Major Geeks still has this version up for grabs!\nAfter several hours of trying and waiting\u0026hellip; SUCCES!!!!\ncackles insanely\n","date":"4 August 2012","externalUrl":null,"permalink":"/2012/08/04/macbooks-bootcamp-windows-7-and-no-optical-drive.../","section":"Posts","summary":"Today my friend and I have been working on installing Windows 7 on a MacBook Pro. Optical drive has been replaced by an SSD drive, and while trying to install Windows, we found out it will not boot from a USB device…\n","title":"MacBooks, BootCamp, Windows 7 and no optical drive...","type":"posts"},{"content":"Had to test something at work today, regarding Group Policies and software installation. It failed on my Windows 7 VM. Couldn\u0026rsquo;t quite figure out why, until I found out the application needs .NET FrameWork version 1.1.\nIt\u0026rsquo;s not supported on Windows 7, but the following link was my rescue: http://saranspot.blogspot.nl/2009/02/installing-dotnet-framework-11-on.html\n","date":"31 July 2012","externalUrl":null,"permalink":"/2012/07/31/windows-7-and-.net-framework-1.1/","section":"Posts","summary":"Had to test something at work today, regarding Group Policies and software installation. It failed on my Windows 7 VM. Couldn’t quite figure out why, until I found out the application needs .NET FrameWork version 1.1.\n","title":"Windows 7 and .NET FrameWork 1.1","type":"posts"},{"content":"Flashed my HTC Desire HD today. Since it is/was branded I had to make a GoldCard first. After that I was able to work magic :)\nI used the info on the following links, in succession:\nhttp://www.addictivetips.com/mobile/how-to-make-gold-card-for-htc-desire-hd/\nhttp://forum.xda-developers.com/showthread.php?t=905003\nhttp://www.addictivetips.com/mobile/how-to-permanently-root-htc-desire-hd-complete-guide/\nhttp://www.addictivetips.com/mobile/how-to-gain-s-off-radio-and-engineering-on-htc-desire-hd/\nhttp://forum.xda-developers.com/showthread.php?t=840040\n:-)\nOh, this one came in handy as well!! - https://www.dropbox.com/s/b7cvmjy71pagq39/ROMs\n","date":"12 October 2011","externalUrl":null,"permalink":"/2011/10/12/flashing-android/","section":"Posts","summary":"Flashed my HTC Desire HD today. Since it is/was branded I had to make a GoldCard first. After that I was able to work magic :)\nI used the info on the following links, in succession:\n","title":"Flashing Android","type":"posts"},{"content":"I\u0026rsquo;ve been busy migrating from BPOS to Office365 last weekend. Manually. Everything went smooth, until one of the COs complained he was missing his appointments in his calendar. I made a backup of every mailbox before moving the items, of course (NOT), and he used his laptop/Outlook after I moved the items from his old mailbox to his new mailbox, so his local (offline) copy was empty as well.\nRecovery by the BPOS-team wasn\u0026rsquo;t an option either, because the mailbox hadn\u0026rsquo;t been deleted, nor disconnected, which are the only two scenarios where they can actually be of any service.\nToday, while looking for a solution, I ran into this link, which saved my day, as well as the lost calendar-items.\nNot sure it the actions described in this link are a pre-requisite, but I\u0026rsquo;m saved. That\u0026rsquo;s what counts for me.\nSo, what have we learned from all this?\nBPOS doesn\u0026rsquo;t provide real backups; when they do a recovery, it will be to the point of where the mailbox was disconnected or deleted\nDon\u0026rsquo;t forget to make backups of mailboxes before moving stuff from it, or copy items, instead of moving them\n","date":"23 August 2011","externalUrl":null,"permalink":"/2011/08/23/thundercloud/","section":"Posts","summary":"I’ve been busy migrating from BPOS to Office365 last weekend. Manually. Everything went smooth, until one of the COs complained he was missing his appointments in his calendar. I made a backup of every mailbox before moving the items, of course (NOT), and he used his laptop/Outlook after I moved the items from his old mailbox to his new mailbox, so his local (offline) copy was empty as well.\n","title":"ThunderCloud?","type":"posts"},{"content":"One of our users got a notification today after sending an email. Said a server with a certain IP-address is listen on a blacklist with Trend Micro RBL+.\nSo I thought to do the right thing (besides requesting to be put off the blacklist) and contact Microsoft Technical Support about it, since this has potential to affect their other customers as well.\nBasically, I was told I should request for the IP-address to be removed from the blacklist, even though it\u0026rsquo;s their server\u0026hellip;.. Weird\u0026hellip;..\n","date":"10 May 2011","externalUrl":null,"permalink":"/2011/05/10/microsoft-cloudmail-server-being-blacklisted-my-problem/","section":"Posts","summary":"One of our users got a notification today after sending an email. Said a server with a certain IP-address is listen on a blacklist with Trend Micro RBL+.\nSo I thought to do the right thing (besides requesting to be put off the blacklist) and contact Microsoft Technical Support about it, since this has potential to affect their other customers as well.\n","title":"Microsoft Cloudmail-server being blacklisted MY problem???","type":"posts"},{"content":"Twiddling a bit with Facebook\u0026hellip;\nRan into this link, which helped me to get the authentication from a WinForms-application working\u0026hellip;\n","date":"3 December 2010","externalUrl":null,"permalink":"/2010/12/03/facebook-and-stuff/","section":"Posts","summary":"Twiddling a bit with Facebook…\nRan into this link, which helped me to get the authentication from a WinForms-application working…\n","title":"Facebook and stuff","type":"posts"},{"content":"While looking up some stuff, I ran into this article. Something to try out someday soon, if you ask me.\n","date":"2 December 2010","externalUrl":null,"permalink":"/2010/12/02/tabified-sharepoint/","section":"Posts","summary":"While looking up some stuff, I ran into this article. Something to try out someday soon, if you ask me.\n","title":"Tabified SharePoint","type":"posts"},{"content":"Well, it seems that, at least for now, my direction in IT will be changed\u0026hellip; again\u0026hellip;\nAt the end of this year, my contract at my current employer will expire and it will not be extended. At the moment, I\u0026rsquo;m very close to signing a contract with a different company, where I will be a system administrator once again, with a hint of SharePoint and network administration, at least for now.\nMy hopes are that in the (near) future, programming will once again be part of my job. Until then, I\u0026rsquo;ll (try to) keep it as a personal interest and maybe even start dabbling with Java and/or Objective-C. (Yes.. I\u0026rsquo;m an Apple fanboy.. Everyone has their flaws, don\u0026rsquo;t they?)\nI\u0026rsquo;ll try to keep this blog updated with relevant information\u0026hellip;\n","date":"30 November 2010","externalUrl":null,"permalink":"/2010/11/30/change-of-direction/","section":"Posts","summary":"Well, it seems that, at least for now, my direction in IT will be changed… again…\nAt the end of this year, my contract at my current employer will expire and it will not be extended. At the moment, I’m very close to signing a contract with a different company, where I will be a system administrator once again, with a hint of SharePoint and network administration, at least for now.\n","title":"Change of direction","type":"posts"},{"content":"This link helped me some in handling password GPO-settings as well\u0026hellip;\nMight clarify some things from the previous post.\n","date":"18 November 2010","externalUrl":null,"permalink":"/2010/11/18/active-directory-and-membershipproviders/","section":"Posts","summary":"This link helped me some in handling password GPO-settings as well…\nMight clarify some things from the previous post.\n","title":"Active Directory and MembershipProviders","type":"posts"},{"content":"When using an ASP.NET-application to change have a user change his/her password, Password Complexity can act up. Or better said\u0026hellip; ASP.NET apparently doesn\u0026rsquo;t look at the settings set in Active Directory :-/\nWhile looking for a solution, I ran into this (series of) article(s)\u0026hellip;.\nMy solution: write a custom MembershipProvider, derived from the ActiveDirectoryMembershipProvider. That way I don\u0026rsquo;t have to actually plug things in the standard-controls, just provide a custom membership-provider\u0026hellip;\n","date":"17 November 2010","externalUrl":null,"permalink":"/2010/11/17/active-directory-and-changing-passwords-through-asp.net/","section":"Posts","summary":"When using an ASP.NET-application to change have a user change his/her password, Password Complexity can act up. Or better said… ASP.NET apparently doesn’t look at the settings set in Active Directory :-/\n","title":"Active Directory and changing passwords through ASP.NET","type":"posts"},{"content":"I\u0026rsquo;m currently fiddling around a bit with UpdatePanels (again), and noticed that focus is lost when the UpdatePanel is updated. Luckily, I found the following site that solved my problem. :)\n","date":"21 October 2010","externalUrl":null,"permalink":"/2010/10/21/updatepanel-updates-and-losing-focus/","section":"Posts","summary":"I’m currently fiddling around a bit with UpdatePanels (again), and noticed that focus is lost when the UpdatePanel is updated. Luckily, I found the following site that solved my problem. :)\n","title":"UpdatePanel-updates and losing focus","type":"posts"},{"content":"So\u0026hellip; apparently SharePoint 2010 has a new \u0026lsquo;feature\u0026rsquo;\u0026hellip; Instead of the English names in URLs, localized names are used.\nFor example, the default-page in 2007 for a Dutch-site has as URL: /Pages/default.aspx\nFor 2010, the URL will be: /Paginas/default.aspx\nWhere the fun comes in? Migrating from a Dutch 2007-site to 2010\u0026hellip; And getting navigation-errors\u0026hellip; and not having all your pages and such\u0026hellip;\nWhat got me on track of this was this post from Appie, developer at TamTam.\nNow all I have to do.. is find a better solution\u0026hellip;\n","date":"15 September 2010","externalUrl":null,"permalink":"/2010/09/15/sharepoint-2010-migrating-from-sharepoint-2007-and-language-packs/","section":"Posts","summary":"So… apparently SharePoint 2010 has a new ‘feature’… Instead of the English names in URLs, localized names are used.\nFor example, the default-page in 2007 for a Dutch-site has as URL: /Pages/default.aspx\n","title":"SharePoint 2010, migrating from SharePoint 2007 and Language Packs","type":"posts"},{"content":"While attempting to find a fix for the migration-thing, I ran into a problem while connecting to the SharePoint site.\nFirst I got an IOFileNotFoundException, stating that the Web application was not found\u0026hellip; So.. Visual Studio 2010 is set to x86 by default\u0026hellip; and SharePoint 2010 is a x64-application. Got that one fixed thanks to this webpage.\nNext, I got an error saying .NET 4 cannot be used for SharePoint\u0026hellip; So I had to change the target framework to .NET 3.5\u0026hellip; Thanks to this page for kicking my brain back into gear\u0026hellip;\nOh.. and VS2010 needs to be run as an administrator\u0026hellip;﻿\n","date":"15 September 2010","externalUrl":null,"permalink":"/2010/09/15/visual-studio-2010-and-sharepoint-2010/","section":"Posts","summary":"While attempting to find a fix for the migration-thing, I ran into a problem while connecting to the SharePoint site.\nFirst I got an IOFileNotFoundException, stating that the Web application was not found… So.. Visual Studio 2010 is set to x86 by default… and SharePoint 2010 is a x64-application. Got that one fixed thanks to this webpage.\n","title":"Visual Studio 2010 and SharePoint 2010","type":"posts"},{"content":"I\u0026rsquo;ve been looking at some code that was written for us which works on one server, but fails on another.\nHas to do with the page not running in the proper context at the time some actions are done.\nLuckily, I found a very good solution right here.\n","date":"15 July 2010","externalUrl":null,"permalink":"/2010/07/15/runwithelevatedprivileges-and-security-validation-errors/","section":"Posts","summary":"I’ve been looking at some code that was written for us which works on one server, but fails on another.\nHas to do with the page not running in the proper context at the time some actions are done.\n","title":"RunWithElevatedPrivileges and security-validation errors","type":"posts"},{"content":"Ran into a little problem having to do with setting some things on a SPGridView-column after databind.\nLuckily I ran into this post, which helped me get a shorter version for the same purpose:\nDataControlFieldCell dcfcDescription = e.Row.Cells.OfType\u0026lt;DataControlFieldCell\u0026gt;().Where(c =\u0026gt; c.ContainingField.SortExpression == \u0026amp;quot;Description\u0026amp;quot;).Select(c =\u0026gt; c).SingleOrDefault(); if (dcfcDescription != null) { Label desc = (Label)dcfcDescription.Controls[0]; dcfcDescription.ToolTip = desc.Text; if (desc != null \u0026amp;amp;\u0026amp;amp; desc.Text.Length \u0026gt; 100) { desc.Text = desc.Text.Substring(0, 50) + \u0026amp;quot; ...\u0026amp;quot;; } } I love being able to use LINQ on objects that were not originally designed for that :D\n","date":"17 February 2010","externalUrl":null,"permalink":"/2010/02/17/linq-is-really-really-nice/","section":"Posts","summary":"Ran into a little problem having to do with setting some things on a SPGridView-column after databind.\nLuckily I ran into this post, which helped me get a shorter version for the same purpose:\n","title":"LINQ is really REALLY nice :)","type":"posts"},{"content":"I was investigating a problem today, where the first week of the year in The Netherlands was show by one of our applications as week 2.\nFortunately, I came across this link, which gave me a solution, and the knowledge that SQL Server 2008 has a native function for this.\n","date":"1 February 2010","externalUrl":null,"permalink":"/2010/02/01/weeknumbers-and-sql-server/","section":"Posts","summary":"I was investigating a problem today, where the first week of the year in The Netherlands was show by one of our applications as week 2.\nFortunately, I came across this link, which gave me a solution, and the knowledge that SQL Server 2008 has a native function for this.\n","title":"Weeknumbers and SQL Server","type":"posts"},{"content":"SPGridView is a really nice control. One of the problems I have encountered with it, is that when you set a column to show its header-text right-aligned, you can wait all you want, it will not happen.\nDid some more searching in the internet today, and found a solution I based my final implementation on.\nFirst, create a private generic List if integers for the application :\nList\u0026lt;int\u0026gt; rightAlignCellsGridView = new List\u0026lt;int\u0026gt;() { }; Now, create the columns and set HeaderStyle.HorizontalAlign to HorizontalAlign.Right for the columns where you want the headertext right-aligned.\nNext, after adding the SPGridView to the controls-collection, use the following:\nforeach (DataControlField col in this.gridView.Columns.OfType\u0026lt;DataControlField\u0026gt;().Where(c =\u0026gt; c.HeaderStyle.HorizontalAlign == HorizontalAlign.Right \u0026amp;amp;\u0026amp;amp; c.Visible == true)) { this.rightAlignColGridView.Add(this.gridView.Columns.IndexOf(col)); } Next, add an event to the SPGridView\u0026rsquo;s RowDataBound-event and add the following code there:\nswitch (e.Row.RowType) { case DataControlRowType.Header: foreach (int idx in rightAlignColGridView) { e.Row.Cells[idx].Style.Add(\u0026amp;quot;text-align\u0026amp;quot;, \u0026amp;quot;right\u0026amp;quot;); } break; default: break; } Now, compile and watch the results\u0026hellip;\n","date":"26 January 2010","externalUrl":null,"permalink":"/2010/01/26/spgridview-and-right-aligning-header-text/","section":"Posts","summary":"SPGridView is a really nice control. One of the problems I have encountered with it, is that when you set a column to show its header-text right-aligned, you can wait all you want, it will not happen.\n","title":"SPGridView and Right-aligning Header-text","type":"posts"},{"content":"I\u0026rsquo;m working on a group-authorization webpart, where I need to display the description of the group. Unfortunately, the Desction-property of SPGroup does not contain what is shown in Sharepoint itself.\nThe actual \u0026lsquo;description\u0026rsquo; is actually the \u0026lsquo;About Me\u0026rsquo;-field. I found this out through this link.\n","date":"19 January 2010","externalUrl":null,"permalink":"/2010/01/19/displaying-sharepoint-group-description/","section":"Posts","summary":"I’m working on a group-authorization webpart, where I need to display the description of the group. Unfortunately, the Desction-property of SPGroup does not contain what is shown in Sharepoint itself.\n","title":"Displaying Sharepoint Group-'description'","type":"posts"},{"content":"While updating the WebPart of my main-project at work, I wanted to find a way to not use BoundField, but still be able to format how my data is displayed.\nThen, I ran into this link.\n","date":"17 December 2009","externalUrl":null,"permalink":"/2009/12/17/adding-formatted-fields-to-a-spgridview/","section":"Posts","summary":"While updating the WebPart of my main-project at work, I wanted to find a way to not use BoundField, but still be able to format how my data is displayed.\n","title":"Adding 'formatted' fields to a SPGridView","type":"posts"},{"content":"I just came across a nice addition to my base web part:\nif (Microsoft.SharePoint.SPContext.Current.FormContext.FormMode == SPControlMode.Display) { // your code to support display mode } else // Microsoft.SharePoint.SPContext.Current.FormContext.FormMode = SPControlMode.Edit { // your code to support edit mode } Since it isn\u0026rsquo;t mine, credits go to this post.\n","date":"14 December 2009","externalUrl":null,"permalink":"/2009/12/14/72/","section":"Posts","summary":"I just came across a nice addition to my base web part:\nif (Microsoft.SharePoint.SPContext.Current.FormContext.FormMode == SPControlMode.Display) { // your code to support display mode } else // Microsoft.SharePoint.SPContext.Current.FormContext.FormMode = SPControlMode.Edit { // your code to support edit mode } Since it isn’t mine, credits go to this post.\n","title":"72","type":"posts"},{"content":"I also came across this nice article (which is also available here and here by another person) which shows how to add a checkbox to the rows in SPGridView for selecting individual rows.\n","date":"3 December 2009","externalUrl":null,"permalink":"/2009/12/03/checkbox-field-sor-selecting-rows-in-spgridview/","section":"Posts","summary":"I also came across this nice article (which is also available here and here by another person) which shows how to add a checkbox to the rows in SPGridView for selecting individual rows.\n","title":"CheckBox-field sor selecting rows in SPGridView","type":"posts"},{"content":"I just ran into a very nice article about using Lambda\u0026rsquo;s/LINQ on SharePoint objects. You can find it here.\n","date":"3 December 2009","externalUrl":null,"permalink":"/2009/12/03/linq/lambda-and-sharepoint/","section":"Posts","summary":"I just ran into a very nice article about using Lambda’s/LINQ on SharePoint objects. You can find it here.\n","title":"LINQ/Lambda and SharePoint","type":"posts"},{"content":"I finally managed to create a solution with all the bells whistling and sort\u0026hellip; as far as I know.\nThe Visual Studio 2008 solution can de found here. The SQL database can be found here. Attach the database in SQL. If you find any problems or have any questions, please leave a comment or send me a mail.\nFor this solution to work, you need to have added the AJAX-entries to the web.config of your MOSS2007 with SP1 installation, as well as the infrastructure-update. Compile and add the compiled DLL\u0026rsquo;s to the BIN-directory of your site. By default, the Debug-version automatically gets compiled to the path \u0026ldquo;C:\\inetpub\\wwwroot\\wss\\VirtualDirectories\\80\\bin\u0026rdquo;. Then, add the correct entries in web.config (SafeControls and Assemblies), then, add the WebParts (DevTinkeringParents en DevTinkeringChilds) to the WebPart-gallery and put them in a WebPart-zone on a page of your liking. ;)\nYou also need to have Visual Studio enhancements for WSS 1.3 installed, because the WebParts are based on the SharePoint WebPart-template.\n","date":"24 November 2009","externalUrl":null,"permalink":"/2009/11/24/solution-file-and-database-for-spgridview-with-working-filtering-sorting-etc./","section":"Posts","summary":"I finally managed to create a solution with all the bells whistling and sort… as far as I know.\nThe Visual Studio 2008 solution can de found here. The SQL database can be found here. Attach the database in SQL. If you find any problems or have any questions, please leave a comment or send me a mail.\n","title":"Solution File and DataBase for SPGridView with working Filtering, Sorting, etc.","type":"posts"},{"content":"Currently I\u0026rsquo;m trying to put together the VS2008 Solution containing the stuff I\u0026rsquo;ve written about the last few weeks. That is\u0026hellip; I\u0026rsquo;m now installing SP2 on my Windows 2008 Virtual Machine, which seems to take forever. Hopefully I can continue work on the solution tomorrow.\n","date":"17 November 2009","externalUrl":null,"permalink":"/2009/11/17/update.../","section":"Posts","summary":"Currently I’m trying to put together the VS2008 Solution containing the stuff I’ve written about the last few weeks. That is… I’m now installing SP2 on my Windows 2008 Virtual Machine, which seems to take forever. Hopefully I can continue work on the solution tomorrow.\n","title":"Update...","type":"posts"},{"content":"I\u0026rsquo;m implementing UpdatePanel and UpdateProgress into my (Base)WebPart and since it is nice to have a \u0026lsquo;while processing\u0026rsquo;-image, I thought about embedding it as a resource.\nThis link helped me to accomplish this.\nThis post helped me to embed the required CSS and JavaScript directly into the page. 4GuysFromRolla have a very nice article here where I got the \u0026lsquo;modal updateprogress\u0026rsquo; from I now use.\nAnd again, when I get somewhere near to a complete thing, I\u0026rsquo;ll post a sample-solution with all this stuff in it.\n","date":"27 October 2009","externalUrl":null,"permalink":"/2009/10/27/using-embedded-resources-in-an-aspx-page/webpart/","section":"Posts","summary":"I’m implementing UpdatePanel and UpdateProgress into my (Base)WebPart and since it is nice to have a ‘while processing’-image, I thought about embedding it as a resource.\nThis link helped me to accomplish this.\n","title":"Using Embedded resources in an ASPX-page/WebPart","type":"posts"},{"content":"Yeah, I know\u0026hellip; I promised to deliver some source-code\u0026hellip;. Still working on the stuff, though\nI\u0026rsquo;m trying to AJAX-ify the WebPart. Since Service Pack 1 both WSS3 and MOSS2007 have AJAX-support. So, I thought I would add an UpdatePanel-control to my WebPart and make things happen. However, there still needs to be some configuration done in the web.config. I ran into a very very helpful link here. Omit the lines with \u0026ldquo;AJAXControlKit\u0026rdquo;. These are not needed, and in fact generate errors. Unless you have the AJAX Control Kit installed\u0026hellip; Which you do not need for UpdatePanel to work\u0026hellip;\nThis link helped me too, in preventing a full postback\u0026hellip;\nThis link provides some more info on UpdatePanels and UpdateProgress and sorts\u0026hellip; also really useful.\n","date":"26 October 2009","externalUrl":null,"permalink":"/2009/10/26/updatepanel-usage-in-sharepoint-sp1-webparts/","section":"Posts","summary":"Yeah, I know… I promised to deliver some source-code…. Still working on the stuff, though\nI’m trying to AJAX-ify the WebPart. Since Service Pack 1 both WSS3 and MOSS2007 have AJAX-support. So, I thought I would add an UpdatePanel-control to my WebPart and make things happen. However, there still needs to be some configuration done in the web.config. I ran into a very very helpful link here. Omit the lines with “AJAXControlKit”. These are not needed, and in fact generate errors. Unless you have the AJAX Control Kit installed… Which you do not need for UpdatePanel to work…\n","title":"UpdatePanel-usage in SharePoint SP1+ WebParts","type":"posts"},{"content":"Ok, seems like I found out where things go wrong about losing filtering when sorting\u0026hellip; I\u0026rsquo;ve disabled the ViewState for the SPGridView, because when a user refreshes his browser after working with things for a while and just returned from a detailsview of entered the detailsview from the gridview, the error \u0026ldquo;Failed to load ViewState\u0026rdquo; appears. This can be solved by disabling the viewstate for the gridview and/or detailsview (depending on what you want to prevent). So now I have to find a way to handle this nicely\u0026hellip;\nOh, by the way\u0026hellip; when I get things sorted out, somewhat, I will post a VS2008 solution with database, so you can see the \u0026lsquo;final thing\u0026rsquo;\u0026hellip;\n","date":"24 September 2009","externalUrl":null,"permalink":"/2009/09/24/spgridview-and-losing-filtering-on-sorting/","section":"Posts","summary":"Ok, seems like I found out where things go wrong about losing filtering when sorting… I’ve disabled the ViewState for the SPGridView, because when a user refreshes his browser after working with things for a while and just returned from a detailsview of entered the detailsview from the gridview, the error “Failed to load ViewState” appears. This can be solved by disabling the viewstate for the gridview and/or detailsview (depending on what you want to prevent). So now I have to find a way to handle this nicely…\n","title":"SPGridView and losing Filtering on Sorting","type":"posts"},{"content":"Ok, so I\u0026rsquo;ve hit one more snag\u0026hellip; When a filter has been set, and a column is clicked to sort, the filter gets lost.\nWhen an SPGridView sorts, it has an empty FilterFieldName. So, we have to see if we can store this somewhere.\nTo be continued\u0026hellip;\n","date":"23 September 2009","externalUrl":null,"permalink":"/2009/09/23/spgridview-linqdatasource-and-filtering...-and-sorting/","section":"Posts","summary":"Ok, so I’ve hit one more snag… When a filter has been set, and a column is clicked to sort, the filter gets lost.\nWhen an SPGridView sorts, it has an empty FilterFieldName. So, we have to see if we can store this somewhere.\n","title":"SPGridView, LinqDataSource and Filtering... and sorting","type":"posts"},{"content":"Finally, I have my filtering working!! :D\nYesterday while I drove home (about an hours drive), I thought of a different approach to my problem; why not try to handle the stuff while applying the filter. Trick, at least for me, was to figure out how to do this.\nBasically, it comes down to NOT supplying the FilteredDataSourcePropertyName and FilteredDataSourcePropertyFormat for the SPGridView and setting the AutoGenerateWhereClause to true.\nThen, at the same spot where the filter is re-applied when not wanting to lose the filter when sorting, handle the filter!\nFor now, still only one filter can be active at a time, opposed to SharePoint-lists, which can have multiple filters active at the same time.\nMy DataBound-event of my SPGridView now looks as follows:\n//First check if there is a filter applied if (string.IsNullOrEmpty(gridview.FilterFieldName) == false) { //Then re-apply the filter with exceptions for specified columns if (gridview.FilterFieldName == \u0026#34;Country\u0026#34;) { if (linqDataSource.WhereParameters[\u0026#34;ChildID\u0026#34;] == null) { linqDataSource.WhereParameters.Add(\u0026#34;ChildID\u0026#34;, System.Data.DbType.Int32, this.dataContext.Childs.Where(c =\u0026gt; c.Country == gridview.FilterFieldValue).Select(c =\u0026gt; c.ChildID).First().ToString()); } } else if (gridview.FilterFieldName.EndsWith(\u0026#34;ID\u0026#34;)) { if (linqDataSource.WhereParameters[gridview.FilterFieldName] == null) { linqDataSource.WhereParameters.Add(gridview.FilterFieldName, System.Data.DbType.Int32, gridview.FilterFieldValue); } } else { if (linqDataSource.WhereParameters[gridview.FilterFieldName] == null) { linqDataSource.WhereParameters.Add(gridview.FilterFieldName, gridview.FilterFieldValue); } } } else if (linqDataSource.WhereParameters.Count \u0026gt; 0) { linqDataSource.WhereParameters.Clear(); } So here\u0026rsquo;s what I\u0026rsquo;m doing. First I check to see if a filter should be applied:\n//First check if there is a filter applied if (string.IsNullOrEmpty(gridview.FilterFieldName) == false) { ... Next, I reapply the filter, but here I also handle my exceptions (for example filtering on integer-values) and my Child-properties. For filtering on the Child-properties, I catch the requested FilterFieldName and add a custom WhereParameter to the LinqDataSource:\n//Then re-apply the filter with exceptions for specified columns if (gridview.FilterFieldName == \u0026#34;Country\u0026#34;) { if (linqDataSource.WhereParameters[\u0026#34;ChildID\u0026#34;] == null) { linqDataSource.WhereParameters.Add(\u0026#34;ChildID\u0026#34;, System.Data.DbType.Int32, this.dataContext.Childs.Where(c =\u0026gt; c.Country == gridview.FilterFieldValue).Select(c =\u0026gt; c.ChildID).First().ToString()); } } Notice that I enter a DbType in the WhereParameter. Because I filter on an integer, I get the error that Operator \u0026lsquo;==\u0026rsquo; incompatible with operand types \u0026lsquo;Int32\u0026rsquo; and \u0026lsquo;String\u0026rsquo; when I ommit this step.\nI also handle the ID-fields to include the DbType in the WhereParameter.\nelse if (gridview.FilterFieldName.EndsWith(\u0026#34;ID\u0026#34;)) { if (linqDataSource.WhereParameters[gridview.FilterFieldName] == null) { linqDataSource.WhereParameters.Add(gridview.FilterFieldName, System.Data.DbType.Int32, gridview.FilterFieldValue); } } For the other columns, I just add a standard WhereParameter where I use the gridview.FilterFieldName als Parameter-name and gridview.FilterFieldValue as it\u0026rsquo;s value.\nelse { if (linqDataSource.WhereParameters[gridview.FilterFieldName] == null) { linqDataSource.WhereParameters.Add(gridview.FilterFieldName, gridview.FilterFieldValue); } } } And if there should be no filtering done, I remove all WhereParameters. Otherwise, clearing a filter will still give filtered results.\nelse if (linqDataSource.WhereParameters.Count \u0026gt; 0) { linqDataSource.WhereParameters.Clear(); } ","date":"15 September 2009","externalUrl":null,"permalink":"/2009/09/15/working-filtering-on-spgridview-and-child-properties/","section":"Posts","summary":"Finally, I have my filtering working!! :D\nYesterday while I drove home (about an hours drive), I thought of a different approach to my problem; why not try to handle the stuff while applying the filter. Trick, at least for me, was to figure out how to do this.\n","title":"Working Filtering on SPGridView and Child-properties","type":"posts"},{"content":"I can get the value from the child-record to show by using the selecting-event of the LinqDataSource and using, for example:\ne.Result = var record from Parent select new { ParentID = record.ParentID, Name = Parent.Name, ChildProperty = Child.Property, ... } Somehow, however, this screws up the filtering-part. Even when I disallow filtering on the columns from the child-record, the SPGridView only gives me \u0026ldquo;Loading\u0026hellip;\u0026rdquo; in the WebPart for the filter. Reason for this seems to be that I had a \u0026ldquo;this.Page.Response.Write\u0026rdquo; in my page. Removing that fixed the initial problem of having no list to select the filter from.\nAt the moment, I\u0026rsquo;ve also added a property in my DataContext-code which returns the child-property (effectively: public string ChildProperty() { get {return this.Child.Property; } }). This replaces the Selecting-eventhandler.\nWhen I want to apply the filter, I now get a new error: The member \u0026lsquo;Child.Property\u0026rsquo; has no supported translation to SQL. I get the same error with the added \u0026lsquo;property\u0026rsquo; in the DataContext-code as with the Selecting-eventhandler.\nSo, anyone with a solution for this problem? I can get it to work if I set the ID I base the actual value on as filter-value, but then, when I show the list of possible values to filter on, I get the values instead of the names\u0026hellip; Which is not the desired effect\u0026hellip;\n","date":"14 September 2009","externalUrl":null,"permalink":"/2009/09/14/filtering-in-spgridview-with-properties-from-the-child-table/","section":"Posts","summary":"I can get the value from the child-record to show by using the selecting-event of the LinqDataSource and using, for example:\ne.Result = var record from Parent select new { ParentID = record.ParentID, Name = Parent.Name, ChildProperty = Child.Property, ... } Somehow, however, this screws up the filtering-part. Even when I disallow filtering on the columns from the child-record, the SPGridView only gives me “Loading…” in the WebPart for the filter. Reason for this seems to be that I had a “this.Page.Response.Write” in my page. Removing that fixed the initial problem of having no list to select the filter from.\n","title":"Filtering in SPGridView with properties from the Child-table","type":"posts"},{"content":"I came across some very nice articles when browsing the internet for some information on how to enable filtering and sorting an an SPGridView with a LinqDataSource as datasource. The most usefull one I found is this one from Johan Leino.\nHowever, there are still some things to be done.\nSince I am writing a WebPart, I have to put things in manually. Which also means that I have to remind myself to give everything an ID. If I don\u0026rsquo; tdo that, filtering will fail, because the methods that retrieve the list of values in the columns won\u0026rsquo;t know where to write back to.\nFor the filtering, the FilteredFieldDataFormat should be: \u0026quot;{1} == \\\u0026quot;{0}\\\u0026quot;\u0026quot; Somehow, the property and value are sent value, property to the format-function :S\nNext, when sorting AND filtering at the same time, we don\u0026rsquo;t want to lose the filter. To accomplish this, we have to add the followin code to the DataBound-event of the SPGridView:\nif (string.IsNullOrEmpty(gridview.FilterFieldName) == false) { linqDataSource.Where = string.Format(\u0026#34;{0} == \\\u0026#34;{1}\\\u0026#34;\u0026#34;,gridview.FilterFieldName,gridview.FilterFieldValue); } If that is done, we possibly would also like to show a filter-icon on the column that is being filtered. Add this code to the RowDataBound-event of the SPGridView:\nif (sender == null || e.Row.RowType != DataControlRowType.Header) { return; } SPGridView grid = sender as SPGridView; if (String.IsNullOrEmpty(grid.FilterFieldName)) { return; } // Show icon on filtered column for (int i = 0; i \u0026lt; grid.Columns.Count; i++) { DataControlField field = grid.Columns[i]; if (field.SortExpression == grid.FilterFieldName) { Image filterIcon = new Image(); filterIcon.ImageUrl = \u0026amp;quot;/_layouts/images/filter.gif\u0026amp;quot;; filterIcon.Style[HtmlTextWriterStyle.MarginLeft] = \u0026amp;quot;2px\u0026amp;quot;; // If we simply add the image to the header cell it will // be placed in front of the title, which is not how it // looks in standard SharePoint. We fix this by the code // below. Literal headerText = new Literal(); headerText.Text = field.HeaderText; PlaceHolder panel = new PlaceHolder(); panel.Controls.Add(headerText); panel.Controls.Add(filterIcon); e.Row.Cells[i].Controls[0].Controls.Add(panel); break; } } Tada!! Now we have an SPGridView with sorting and filtering.\nNow to find a way to do this with Parent-Child-relations and not havin the ID\u0026rsquo;s in the SPGridView, but the wanted info from the Child-table AND have sorting and filtering\u0026hellip;\nTo be continued\u0026hellip;.\n","date":"9 September 2009","externalUrl":null,"permalink":"/2009/09/09/spgridview-sorting-filtering-linq...-webparts......./","section":"Posts","summary":"I came across some very nice articles when browsing the internet for some information on how to enable filtering and sorting an an SPGridView with a LinqDataSource as datasource. The most usefull one I found is this one from Johan Leino.\n","title":"SPGridView, Sorting, Filtering, Linq... WebParts.......","type":"posts"},{"content":"My name is Sebastiaan Brozius. I\u0026rsquo;m an IT-guy since 1996 and have seen several sides of our profession.\nStarting out as assembler (we built our own PC\u0026rsquo;s at my first employer), I grew to be a system engineer. After that I\u0026rsquo;ve also been a network engineer, a developer and then went back to being a system administrator.\nI love being challenged as it makes you and the other grow and improve yourself and processes.\nCurrently I\u0026rsquo;m focusing on AWS and DevOps, working to become more proficient and comfortable with all that AWS offers.\nWhen I\u0026rsquo;m off-work I enjoy playing saxophone, gaming on the PC, watching series (sci-fi, fantasy), bushcraft and occasionally playing some Magic: the Gathering with friends.\n","externalUrl":null,"permalink":"/about/","section":"Just Tinkering","summary":"My name is Sebastiaan Brozius. I’m an IT-guy since 1996 and have seen several sides of our profession.\nStarting out as assembler (we built our own PC’s at my first employer), I grew to be a system engineer. After that I’ve also been a network engineer, a developer and then went back to being a system administrator.\n","title":"About me","type":"page"},{"content":"","externalUrl":null,"permalink":"/authors/","section":"Authors","summary":"","title":"Authors","type":"authors"},{"content":"","externalUrl":null,"permalink":"/categories/","section":"Categories","summary":"","title":"Categories","type":"categories"},{"content":" Summary # Currently Cloud Engineer / Cloud Consultant at CloudNation. I have experience in several different areas of IT. Passion for scripting/developing. I love being challenged as it makes both you and the other grow, and helps to improve processes. I've taken up bushcraft as a hobby, and I am also a volunteer for a bushcraft organisation, where I'm supporting the instructors during courses, as well as learning new things along the way, every time. Work Experience # Cloud Consultant / Cloud Engineer May 2022 - present CloudNation, Bunnik Working on an IoT based solution for managing hand-held scanners at customer locations. Setting up CloudWatch alarms for Redis, EFS, RDS, ELB Helping to create a migration plan for migrating systems from a data center to AWS, as well as creating a list of systems that will be impacted, both in the data center and at customer sites. Helping a customer with some issues; troubleshooting missing logs of ECS containers, exporting container metrics to AWS Managed Prometheus to be digested by AWS Managed Grafana, migrating accounts form multiple AWS organisations into a single AWS organisation. Helping a customer migrate their existing application to AWS ECS and setting up a landing zone, infrastructure and pipeline, using GitHub, Terraform and org-formation. Working on projects for multiple customers, helping them set-up and/or improve their AWS environment. Deploying landing-zones in AWS using org-formation. Deploying infrastructure using Terraform. Migrating Linux-hosts from a data centre to AWS EC2 instances using Terraform for setting up the infrastructure, AWS Migration Hub and AWS Application Migration Services. Helping a customer migrate their existing application to AWS ECS FarGate using Terraform and GitHub. Designing and deploying an environment for a customer to run application workloads, as well as a solution to migrate existing data to S3 and keeping it in sync with on-prem, using several AWS services, Synology and Terraform. Deploying an environment with EC2 instances, VPC peering and site-to-site VPN, for hosting temporary Oracle Database servers, using Terraform. Improving existing processes and templates and developing new ones, using Terraform, Python, CloudFormation and org-formation. Providing documentation and how-to's for both customer and co-worker reference. Providing support/troubleshooting when customers have issues in their AWS environment. Senior Cloud Engineer June 2020 - April 2022 Solvinity, Amersfoort Member of the Public Cloud team, developing solutions and supporting the customer teams with the implementation of Cloud solutions. Creating an AWS Landing Zone template using Terraform. Integrating OpsGenie with ServiceNow using Terraform and JavaScript. Giving demos and helping co-workers with Terraform and deploying infrastructure using CI/CD pipelines. Supporting co-workers issues in AWS and Azure. Creating a workflow for promoting operating system base-images through DTAP in an Oracle Cloud Infrastructure environment, using Ansible, Terraform and GitLab pipelines. Senior Windows Engineer June 2016 - May 2020 Solvinity, Assen Part of one of the customer teams; troubleshooting, maintenance, implementing new solutions, on-boarding new customers and setting up new environments. Implemented a Microsoft SQL Always-On cluster. Setting up a development-environment with virtual machines in Azure, using PowerShell. On-call duty several times a year for the entire company. Setting up new customer environments using PowerShell, VMware vCenter and Microsoft Hyper-V. Migrating customer environments to our platform, including both lift \\\u0026 shift migrations and greenfield deployments. Automating tasks using PowerShell, e.g. installing new server-certificates, automated fail-over of an Microsoft SQL Always-On cluster for maintenance including a PowerShell module for managing the cluster. Troubleshooting customer-issues and management of customer environments, using PowerShell, Microosft Exchange, VMware vCenter, Microsoft Hyper-V, Linux, Windows Server, Microsoft SQL, Cisco ASA Firewalls, PaloAlto firewalls, Shavlik Patch Management. Preparing the weekly team sprint-planning. System Administrator/Technical Specialist June 2014 - June 2016 NORISK IT Group, Groningen Managing a large customer-environment with multiple branch-offices. This included providing second-line support, responsible for backups, updating several applications, managing Exchange 2010 and managing the Citrix environment. Technologies used: Windows Server 2008, Windows Server 2008 R2, Windows Server 2012, VMWare vSphere 5, Exchange 2010, RES Workspace Manager, Citrix XenApp 6.5, Citrix PVS. Managing customer environments in our own private cloud, based on VMware. Technologies used: Windows Server 2012 (R2), Exchange 2010 en 2013, RES ONE Workspace 2012 and up, Citrix XenApp 7 Worked on a project to set up a private cloud-environment based on Microsoft Hyper-V and Microsoft Azure Pack. Migration of an on-prem customer environment to our private cloud based on Microsoft Hyper-V and Microsoft Azure Pack. Technologies used: Windows Server 2012 R2, Exchange 2013, Microsoft Remote Desktop Services, Microsoft Azure Multi-Factor Authentication Services, RES ONE Workspace 2015. Implementing CommVault Simpana 10 in our private cloud environment and office environment. Creating scripts for installation of servers and applications, using PowerShell, batch-files, AutoIT. Creating scripts for reporting on our customer environments and billing. Setting up monitoring using Nagios. System Administrator July 2012 - May 2014 XLence Companies, Hoevelaken/Groningen Assisting a customer in the migration of 6 environment into a single environment, and providing systems administration on it. This included providing second-line support, responsible for backups, updating several applications, managing Exchange 2010 and managing the Citrix environment. Technologies used: Windows Server 2003, Windows Server 2008 R2, DPM 2010, Citrix XenServer 6, VMWare ESX, Exchange 2010, ISA Server 2006, Citrix MetaFrame, Microsoft Terminal Services, Symantec Backup Exec, RES Workspace Manager. Project Member November 2011 - June 2012 XLence Companies, Hoevelaken/Groningen Installing and configuring internet-access at care locations throughout the Netherlands for a large healthcare organisation. Responsibilities included measurements for installing WiFi, requesting internet-connectivity with several providers, installing and configuring hardware for WiFi, communication with the care locations, clients (with an intellectual disability), regional managers, etc. Worked with: HP ProCurve Access Points, HP ProCurve Wireless Controller, Windows XP, Ekahau Site Survey, FritzBox, DrayTek. Project Member/Teamlead February 2011 - November 2011 XLence Companies, Hoevelaken/Groningen Preparing locations throughout the Netherlands for a migration, for a large healthcare organisation. Responsibilities included managing the team members, communicating the the client, planning, checking compliancy of the workstations and printers and fix or replace those that didn't comply. Worked with: Windows XP, Windows 7, RES Wisdom, Citrix XenApp Client, HP Printers, Microsoft Office 2003. System Administrator February 2011 - May 2014 XLence Companies, Hoevelaken/Groningen Migrating several customers to Office365, including providing support to the users, server management and migrating Exchange to Office365. Software Developer January 2009 - December 2010 Koops Furness, Zwolle Responsible for, and development of an aplication to report financial information from several sources (Darts, iSeries, ADP EVA DMS) within SharePoint 2007, using Visual Studio 2008/2010, Visual C\\# and ASP.NET. Supporting system administrators/'guru' with Tivoli Storage Manager, Windows Server, SAN, Cisco, RES PowerFuse, Active Directory, Group Policies. Setting up a development environment based on Windows 2008, SharePoint 2007, Team Foundation Server 2005 and Tivoli Storage Manager. Junior Software Developer November 2008 - December 2008 4dotnet, Meppel Getting certified as a .NET developer. Troubleshooting a custom-made program for doing maintenance in Maximizer. Junior Software Developer January 2007 - October 2008 Cosmo Trucks (part of Koops Furness-group), Zwolle Setting up and documenting a new intranet, based on Microsoft SharePoint 2007, ASP.NET and C#. Developing several Microsoft Office AddIns. Developing a tool for controlled distribution of updates to laptop users. Network Engineer February 2003 - Decemer 2006 Cosmo Trucks (part of Koops Furness-group), Zwolle Project member for implementing a Terminal Server environment based on Windows Server 2003, PowerFuse 7 and Softricity SoftGrid 3.0. Managing the backup-server, based on IBM Tivoli Storage Manager. Responsible for the network equipment in all offices. Responsible for all WAN connections between the 37 branch-offices and HQ. Developing a script for automatic installation of workstations. System Administrator March 2002 - January 2003 Cosmo Trucks (part of Koops Furness-group), Zwolle Responsible for the workstations throughout the Netherlands (approx. 650). Responsible for all servers and Windows-domains. System Administrator July 2001 - December 2001 AdVo ICT Professionals, Houten Responsible for implementing Terminal Server environments at several primary schools, based on Windows Server 2000. Regular on-site system administration for customers. On-site second and third line support. System Engineer 1999 - June 2001 CAM IT Solutions, Nieuwegein Deploying new network environment (including at a building site for a new hospital in Hoorn). On-site regular system administration for customers. On-site and in-house second and third line support. Helpdesk Employee 1998 - 1999 CAM IT Solutions, Utrecht Assembly worker June 1996 - 1997 CAM IT Solutions, Utrecht Certifications # Amazon Web Services Active AWS Certified DevOps Engineer - Professional (DOP) (095b195a571e4ee285b205fda6a107db)\nAWS Certified SysOps Administrator - Associate (SOA) (8NQSY6310NQ4QD9K)\nAWS Certified Advanced Networking - Specialty (ANS) (3S70P042WJVQQEK4)\nAWS Certified Security - Specialty (SCS) (FMMR5DPBPNQ4QZCP)\nAWS Certified Solutions Architect - Professional (SAP) (MS13T0C1Z1R1QBG3)\nAWS Certified Solutions Architect - Associate (SAA) (NCWDS31JH1Q114CN)\nAWS Certified Developer - Associate (DVA) (THFEDELJ22EE1PGD)\nAWS Certified Cloud Practitioner (CLF) (W7KKRC6DN24E1TKS)\nMicrosoft Active Microsoft Certified Solutions Associate\nMicrosoft Certified: Azure Fundamentals\nVMware Active VMware Certified Professional 6 – Data Center Virtualization (VMW-01283488T-00443853)\nSkills # DevOps AWS, Azure, Terraform, CI/CD, OpsGenie, Ansible, Docker, TeamCity, git, GitHub, GitLab, Puppet\nProgramming PowerShell, bash, JavaScript, Python, C#\nInfrastructure Networking, Hyper-V, VMware, AWS, Azure, Terraform\nOther Commvault Simpana 10, Zerto\nLanguages Dutch (native), English (advanced)\nEducation # MBO Telematica, Scutos, Utrecht 1993 - 1996 HAVO, Minkema College, Woerden 1991 - 1993 MAVO, Minkema College, Woerden 1987 - 1991 Extra Curricular # AWS Community Builder, Amazon Web Services February 2023 - present The AWS Community Builders program offers technical resources, education, and networking opportunities to AWS technical enthusiasts and emerging thought leaders who are passionate about sharing knowledge and connecting with the technical community.\nVolunteer, Bushcraft Nederland January 2026 - present Assisting during bushcraft courses, supporting the instructors and participants.\nVolunteer, Siegurd.nl January 2021 - December 2025 Assisting during bushcraft courses, supporting the instructors and participants.\n","externalUrl":null,"permalink":"/resume/","section":"Just Tinkering","summary":"Summary # Currently Cloud Engineer / Cloud Consultant at CloudNation. I have experience in several different areas of IT. Passion for scripting/developing. I love being challenged as it makes both you and the other grow, and helps to improve processes. I've taken up bushcraft as a hobby, and I am also a volunteer for a bushcraft organisation, where I'm supporting the instructors during courses, as well as learning new things along the way, every time. Work Experience # Cloud Consultant / Cloud Engineer May 2022 - present CloudNation, Bunnik Working on an IoT based solution for managing hand-held scanners at customer locations. Setting up CloudWatch alarms for Redis, EFS, RDS, ELB Helping to create a migration plan for migrating systems from a data center to AWS, as well as creating a list of systems that will be impacted, both in the data center and at customer sites. Helping a customer with some issues; troubleshooting missing logs of ECS containers, exporting container metrics to AWS Managed Prometheus to be digested by AWS Managed Grafana, migrating accounts form multiple AWS organisations into a single AWS organisation. Helping a customer migrate their existing application to AWS ECS and setting up a landing zone, infrastructure and pipeline, using GitHub, Terraform and org-formation. Working on projects for multiple customers, helping them set-up and/or improve their AWS environment. Deploying landing-zones in AWS using org-formation. Deploying infrastructure using Terraform. Migrating Linux-hosts from a data centre to AWS EC2 instances using Terraform for setting up the infrastructure, AWS Migration Hub and AWS Application Migration Services. Helping a customer migrate their existing application to AWS ECS FarGate using Terraform and GitHub. Designing and deploying an environment for a customer to run application workloads, as well as a solution to migrate existing data to S3 and keeping it in sync with on-prem, using several AWS services, Synology and Terraform. Deploying an environment with EC2 instances, VPC peering and site-to-site VPN, for hosting temporary Oracle Database servers, using Terraform. Improving existing processes and templates and developing new ones, using Terraform, Python, CloudFormation and org-formation. Providing documentation and how-to's for both customer and co-worker reference. Providing support/troubleshooting when customers have issues in their AWS environment. Senior Cloud Engineer June 2020 - April 2022 Solvinity, Amersfoort Member of the Public Cloud team, developing solutions and supporting the customer teams with the implementation of Cloud solutions. Creating an AWS Landing Zone template using Terraform. Integrating OpsGenie with ServiceNow using Terraform and JavaScript. Giving demos and helping co-workers with Terraform and deploying infrastructure using CI/CD pipelines. Supporting co-workers issues in AWS and Azure. Creating a workflow for promoting operating system base-images through DTAP in an Oracle Cloud Infrastructure environment, using Ansible, Terraform and GitLab pipelines. Senior Windows Engineer June 2016 - May 2020 Solvinity, Assen Part of one of the customer teams; troubleshooting, maintenance, implementing new solutions, on-boarding new customers and setting up new environments. Implemented a Microsoft SQL Always-On cluster. Setting up a development-environment with virtual machines in Azure, using PowerShell. On-call duty several times a year for the entire company. Setting up new customer environments using PowerShell, VMware vCenter and Microsoft Hyper-V. Migrating customer environments to our platform, including both lift \\\u0026 shift migrations and greenfield deployments. Automating tasks using PowerShell, e.g. installing new server-certificates, automated fail-over of an Microsoft SQL Always-On cluster for maintenance including a PowerShell module for managing the cluster. Troubleshooting customer-issues and management of customer environments, using PowerShell, Microosft Exchange, VMware vCenter, Microsoft Hyper-V, Linux, Windows Server, Microsoft SQL, Cisco ASA Firewalls, PaloAlto firewalls, Shavlik Patch Management. Preparing the weekly team sprint-planning. System Administrator/Technical Specialist June 2014 - June 2016 NORISK IT Group, Groningen Managing a large customer-environment with multiple branch-offices. This included providing second-line support, responsible for backups, updating several applications, managing Exchange 2010 and managing the Citrix environment. Technologies used: Windows Server 2008, Windows Server 2008 R2, Windows Server 2012, VMWare vSphere 5, Exchange 2010, RES Workspace Manager, Citrix XenApp 6.5, Citrix PVS. Managing customer environments in our own private cloud, based on VMware. Technologies used: Windows Server 2012 (R2), Exchange 2010 en 2013, RES ONE Workspace 2012 and up, Citrix XenApp 7 Worked on a project to set up a private cloud-environment based on Microsoft Hyper-V and Microsoft Azure Pack. Migration of an on-prem customer environment to our private cloud based on Microsoft Hyper-V and Microsoft Azure Pack. Technologies used: Windows Server 2012 R2, Exchange 2013, Microsoft Remote Desktop Services, Microsoft Azure Multi-Factor Authentication Services, RES ONE Workspace 2015. Implementing CommVault Simpana 10 in our private cloud environment and office environment. Creating scripts for installation of servers and applications, using PowerShell, batch-files, AutoIT. Creating scripts for reporting on our customer environments and billing. Setting up monitoring using Nagios. System Administrator July 2012 - May 2014 XLence Companies, Hoevelaken/Groningen Assisting a customer in the migration of 6 environment into a single environment, and providing systems administration on it. This included providing second-line support, responsible for backups, updating several applications, managing Exchange 2010 and managing the Citrix environment. Technologies used: Windows Server 2003, Windows Server 2008 R2, DPM 2010, Citrix XenServer 6, VMWare ESX, Exchange 2010, ISA Server 2006, Citrix MetaFrame, Microsoft Terminal Services, Symantec Backup Exec, RES Workspace Manager. Project Member November 2011 - June 2012 XLence Companies, Hoevelaken/Groningen Installing and configuring internet-access at care locations throughout the Netherlands for a large healthcare organisation. Responsibilities included measurements for installing WiFi, requesting internet-connectivity with several providers, installing and configuring hardware for WiFi, communication with the care locations, clients (with an intellectual disability), regional managers, etc. Worked with: HP ProCurve Access Points, HP ProCurve Wireless Controller, Windows XP, Ekahau Site Survey, FritzBox, DrayTek. Project Member/Teamlead February 2011 - November 2011 XLence Companies, Hoevelaken/Groningen Preparing locations throughout the Netherlands for a migration, for a large healthcare organisation. Responsibilities included managing the team members, communicating the the client, planning, checking compliancy of the workstations and printers and fix or replace those that didn't comply. Worked with: Windows XP, Windows 7, RES Wisdom, Citrix XenApp Client, HP Printers, Microsoft Office 2003. System Administrator February 2011 - May 2014 XLence Companies, Hoevelaken/Groningen Migrating several customers to Office365, including providing support to the users, server management and migrating Exchange to Office365. Software Developer January 2009 - December 2010 Koops Furness, Zwolle Responsible for, and development of an aplication to report financial information from several sources (Darts, iSeries, ADP EVA DMS) within SharePoint 2007, using Visual Studio 2008/2010, Visual C\\# and ASP.NET. Supporting system administrators/'guru' with Tivoli Storage Manager, Windows Server, SAN, Cisco, RES PowerFuse, Active Directory, Group Policies. Setting up a development environment based on Windows 2008, SharePoint 2007, Team Foundation Server 2005 and Tivoli Storage Manager. Junior Software Developer November 2008 - December 2008 4dotnet, Meppel Getting certified as a .NET developer. Troubleshooting a custom-made program for doing maintenance in Maximizer. Junior Software Developer January 2007 - October 2008 Cosmo Trucks (part of Koops Furness-group), Zwolle Setting up and documenting a new intranet, based on Microsoft SharePoint 2007, ASP.NET and C#. Developing several Microsoft Office AddIns. Developing a tool for controlled distribution of updates to laptop users. Network Engineer February 2003 - Decemer 2006 Cosmo Trucks (part of Koops Furness-group), Zwolle Project member for implementing a Terminal Server environment based on Windows Server 2003, PowerFuse 7 and Softricity SoftGrid 3.0. Managing the backup-server, based on IBM Tivoli Storage Manager. Responsible for the network equipment in all offices. Responsible for all WAN connections between the 37 branch-offices and HQ. Developing a script for automatic installation of workstations. System Administrator March 2002 - January 2003 Cosmo Trucks (part of Koops Furness-group), Zwolle Responsible for the workstations throughout the Netherlands (approx. 650). Responsible for all servers and Windows-domains. System Administrator July 2001 - December 2001 AdVo ICT Professionals, Houten Responsible for implementing Terminal Server environments at several primary schools, based on Windows Server 2000. Regular on-site system administration for customers. On-site second and third line support. System Engineer 1999 - June 2001 CAM IT Solutions, Nieuwegein Deploying new network environment (including at a building site for a new hospital in Hoorn). On-site regular system administration for customers. On-site and in-house second and third line support. Helpdesk Employee 1998 - 1999 CAM IT Solutions, Utrecht Assembly worker June 1996 - 1997 CAM IT Solutions, Utrecht Certifications # Amazon Web Services Active AWS Certified DevOps Engineer - Professional (DOP) (095b195a571e4ee285b205fda6a107db)\nAWS Certified SysOps Administrator - Associate (SOA) (8NQSY6310NQ4QD9K)\nAWS Certified Advanced Networking - Specialty (ANS) (3S70P042WJVQQEK4)\nAWS Certified Security - Specialty (SCS) (FMMR5DPBPNQ4QZCP)\nAWS Certified Solutions Architect - Professional (SAP) (MS13T0C1Z1R1QBG3)\nAWS Certified Solutions Architect - Associate (SAA) (NCWDS31JH1Q114CN)\nAWS Certified Developer - Associate (DVA) (THFEDELJ22EE1PGD)\nAWS Certified Cloud Practitioner (CLF) (W7KKRC6DN24E1TKS)\nMicrosoft Active Microsoft Certified Solutions Associate\nMicrosoft Certified: Azure Fundamentals\nVMware Active VMware Certified Professional 6 – Data Center Virtualization (VMW-01283488T-00443853)\nSkills # DevOps AWS, Azure, Terraform, CI/CD, OpsGenie, Ansible, Docker, TeamCity, git, GitHub, GitLab, Puppet\nProgramming PowerShell, bash, JavaScript, Python, C#\nInfrastructure Networking, Hyper-V, VMware, AWS, Azure, Terraform\nOther Commvault Simpana 10, Zerto\nLanguages Dutch (native), English (advanced)\nEducation # MBO Telematica, Scutos, Utrecht 1993 - 1996 HAVO, Minkema College, Woerden 1991 - 1993 MAVO, Minkema College, Woerden 1987 - 1991 ","title":"Resume","type":"page"},{"content":"","externalUrl":null,"permalink":"/series/","section":"Series","summary":"","title":"Series","type":"series"}]