diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..680d4e4 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,34 @@ +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.6.0 # Get the latest from: https://github.com/pre-commit/pre-commit-hooks/releases + hooks: + - id: no-commit-to-branch + - id: check-yaml + args: [--unsafe] + - id: check-json + - id: mixed-line-ending + - id: end-of-file-fixer + - id: trailing-whitespace + - id: check-added-large-files + - id: pretty-format-json + args: ['--autofix'] + - repo: https://github.com/aws-cloudformation/cfn-lint + rev: v1.44.0 + hooks: + - id: cfn-lint + files: infra/cfn/.*\\.(json|yml|yaml)$ + - repo: https://github.com/igorshubovych/markdownlint-cli + rev: v0.41.0 + hooks: + - id: markdownlint + name: "Checking Markdown with markdownlint" + - repo: https://github.com/Yelp/detect-secrets + rev: v1.5.0 + hooks: + - id: detect-secrets + args: ['--baseline', '.secrets.baseline'] + exclude: package.lock.json + - repo: https://github.com/psf/black + rev: 24.4.2 + hooks: + - id: black diff --git a/.secrets.baseline b/.secrets.baseline new file mode 100644 index 0000000..7fd200f --- /dev/null +++ b/.secrets.baseline @@ -0,0 +1,150 @@ +{ + "version": "1.5.0", + "plugins_used": [ + { + "name": "ArtifactoryDetector" + }, + { + "name": "AWSKeyDetector" + }, + { + "name": "AzureStorageKeyDetector" + }, + { + "name": "Base64HighEntropyString", + "limit": 4.5 + }, + { + "name": "BasicAuthDetector" + }, + { + "name": "CloudantDetector" + }, + { + "name": "DiscordBotTokenDetector" + }, + { + "name": "GitHubTokenDetector" + }, + { + "name": "GitLabTokenDetector" + }, + { + "name": "HexHighEntropyString", + "limit": 3.0 + }, + { + "name": "IbmCloudIamDetector" + }, + { + "name": "IbmCosHmacDetector" + }, + { + "name": "IPPublicDetector" + }, + { + "name": "JwtTokenDetector" + }, + { + "name": "KeywordDetector", + "keyword_exclude": "" + }, + { + "name": "MailchimpDetector" + }, + { + "name": "NpmDetector" + }, + { + "name": "OpenAIDetector" + }, + { + "name": "PrivateKeyDetector" + }, + { + "name": "PypiTokenDetector" + }, + { + "name": "SendGridDetector" + }, + { + "name": "SlackDetector" + }, + { + "name": "SoftlayerDetector" + }, + { + "name": "SquareOAuthDetector" + }, + { + "name": "StripeDetector" + }, + { + "name": "TelegramBotTokenDetector" + }, + { + "name": "TwilioKeyDetector" + } + ], + "filters_used": [ + { + "path": "detect_secrets.filters.allowlist.is_line_allowlisted" + }, + { + "path": "detect_secrets.filters.common.is_baseline_file", + "filename": ".secrets.baseline" + }, + { + "path": "detect_secrets.filters.common.is_ignored_due_to_verification_policies", + "min_level": 2 + }, + { + "path": "detect_secrets.filters.heuristic.is_indirect_reference" + }, + { + "path": "detect_secrets.filters.heuristic.is_likely_id_string" + }, + { + "path": "detect_secrets.filters.heuristic.is_lock_file" + }, + { + "path": "detect_secrets.filters.heuristic.is_not_alphanumeric_string" + }, + { + "path": "detect_secrets.filters.heuristic.is_potential_uuid" + }, + { + "path": "detect_secrets.filters.heuristic.is_prefixed_with_dollar_sign" + }, + { + "path": "detect_secrets.filters.heuristic.is_sequential_string" + }, + { + "path": "detect_secrets.filters.heuristic.is_swagger_file" + }, + { + "path": "detect_secrets.filters.heuristic.is_templated_secret" + } + ], + "results": { + "Makefile": [ + { + "type": "Secret Keyword", + "filename": "Makefile", + "hashed_secret": "504a5c3a2ce5fa0acc5f8d9685dc45860c25a8ed", + "is_verified": false, + "line_number": 16, + "is_secret": false + }, + { + "type": "Secret Keyword", + "filename": "Makefile", + "hashed_secret": "99c849bb78a99cb7d6c13e159d4f052f933efa49", + "is_verified": false, + "line_number": 17, + "is_secret": false + } + ] + }, + "generated_at": "2026-02-22T16:05:34Z" +} diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..c2ba9a2 --- /dev/null +++ b/Makefile @@ -0,0 +1,19 @@ +.PHONY: install run + + +install: + brew install pre-commit gawk coreutils cfn-lint + brew install checkov semgrep markdownlint-cli shellcheck + pre-commit install + +run: + pre-commit run -a + +update: + pre-commit autoupdate + +audit-secrets: + detect-secrets scan > .secrets.baseline + detect-secrets audit .secrets.baseline + +default: run diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/infra/cfn/nas-backup.yml b/infra/cfn/nas-backup.yml new file mode 100644 index 0000000..d4ebf88 --- /dev/null +++ b/infra/cfn/nas-backup.yml @@ -0,0 +1,142 @@ +AWSTemplateFormatVersion: '2010-09-09' +Description: nas-backup stack - Corrected for IAM runtime variables and validation. + +Parameters: + environment: + Type: String + Default: prod + AllowedValues: [dev, stg, prod] + Description: Deployment environment (must be lowercase). + +Resources: + # S3 Bucket + NASBackupBucket: + Type: 'AWS::S3::Bucket' + Properties: + BucketName: !Sub 'nas-backup-${environment}-${AWS::AccountId}' + PublicAccessBlockConfiguration: + BlockPublicAcls: true + BlockPublicPolicy: true + IgnorePublicAcls: true + RestrictPublicBuckets: true + BucketEncryption: + ServerSideEncryptionConfiguration: + - ServerSideEncryptionByDefault: + SSEAlgorithm: AES256 + LifecycleConfiguration: + Rules: + - Id: CleanIncompleteMultipartUploads + Status: Enabled + AbortIncompleteMultipartUpload: + DaysAfterInitiation: 1 + Tags: + - Key: 'cloudspout:environment' + Value: !Ref environment + - Key: 'cloudspout:project' + Value: 'nas-backup' + - Key: 'cloudspout:repository' + Value: 'cloudspout/nas-backup' + + # IAM User + NASBackupUser: + Type: 'AWS::IAM::User' + Properties: + UserName: !Sub 'nas-backup-${environment}' + Tags: + - Key: 'cloudspout:environment' + Value: !Ref environment + - Key: 'cloudspout:project' + Value: 'nas-backup' + - Key: 'cloudspout:repository' + Value: 'cloudspout/nas-backup' + - Key: 'cloudspout:home-ip' + Value: '0.0.0.0/0' + - Key: 'cloudspout:fallback-ip' + Value: '0.0.0.0/0' + + # IAM Managed Policy - Using ${!} to escape IAM runtime variables + NASBackupManagedPolicy: + Type: 'AWS::IAM::ManagedPolicy' + Properties: + ManagedPolicyName: !Sub 'nas-backup-policy-${environment}' + Users: [!Ref NASBackupUser] + PolicyDocument: + Version: '2012-10-17' + Statement: + # BLOCK 1: Global Account Listing + - Effect: Allow + Action: ['s3:ListAllMyBuckets'] + Resource: '*' + Condition: + IpAddress: + 'aws:SourceIp': + - '${aws:PrincipalTag/cloudspout:home-ip}' + - Effect: Allow + Action: ['s3:ListAllMyBuckets'] + Resource: '*' + Condition: + IpAddress: + 'aws:SourceIp': + - '${aws:PrincipalTag/cloudspout:fallback-ip}' + + # BLOCK 2: Home IP Access + - Effect: Allow + Action: + - 's3:ListBucket' + - 's3:GetBucketLocation' + - 's3:ListBucketMultipartUploads' + - 's3:PutObject' + - 's3:GetObject' + - 's3:DeleteObject' + - 's3:AbortMultipartUpload' + - 's3:ListMultipartUploadParts' + Resource: + - !GetAtt NASBackupBucket.Arn + - !Sub '${NASBackupBucket.Arn}/*' + Condition: + IpAddress: + 'aws:SourceIp': '${aws:PrincipalTag/cloudspout:home-ip}' + + # BLOCK 3: Fallback IP Access + - Effect: Allow + Action: + - 's3:ListBucket' + - 's3:GetBucketLocation' + - 's3:ListBucketMultipartUploads' + - 's3:PutObject' + - 's3:GetObject' + - 's3:DeleteObject' + - 's3:AbortMultipartUpload' + - 's3:ListMultipartUploadParts' + Resource: + - !GetAtt NASBackupBucket.Arn + - !Sub '${NASBackupBucket.Arn}/*' + Condition: + IpAddress: + 'aws:SourceIp': '${aws:PrincipalTag/cloudspout:fallback-ip}' + + NASBackupUserAccessKey: + Type: 'AWS::IAM::AccessKey' + Properties: + UserName: !Ref NASBackupUser + + NASBackupSecret: + Type: 'AWS::SecretsManager::Secret' + Properties: + Name: !Sub 'nas-backup-credentials-${environment}' + SecretString: !Sub | + { + "AccessKeyId": "${NASBackupUserAccessKey}", + "SecretAccessKey": "${NASBackupUserAccessKey.SecretAccessKey}" + } + Tags: + - Key: 'cloudspout:environment' + Value: !Ref environment + - Key: 'cloudspout:project' + Value: 'nas-backup' + +Outputs: + BucketName: + Value: !Ref NASBackupBucket + SecretARN: + Value: !Ref NASBackupSecret diff --git a/infra/cloudformation.yml b/infra/cloudformation.yml deleted file mode 100644 index 2e32fed..0000000 --- a/infra/cloudformation.yml +++ /dev/null @@ -1,106 +0,0 @@ -AWSTemplateFormatVersion: '2010-09-09' -Description: AWS CloudFormation for nas-backup with complete Multipart and Location permissions. - -Parameters: - environment: - Type: String - Default: prod - AllowedValues: - - dev - - stg - - prod - Description: The deployment environment (must be lowercase). - -Resources: - # S3 Bucket - NASBackupBucket: - Type: 'AWS::S3::Bucket' - Properties: - BucketName: !Sub 'nas-backup-${environment}-${AWS::AccountId}' - PublicAccessBlockConfiguration: - BlockPublicAcls: true - BlockPublicPolicy: true - IgnorePublicAcls: true - RestrictPublicBuckets: true - BucketEncryption: - ServerSideEncryptionConfiguration: - - ServerSideEncryptionByDefault: - SSEAlgorithm: AES256 - LifecycleConfiguration: - Rules: - - Id: CleanIncompleteMultipartUploads - Status: Enabled - AbortIncompleteMultipartUpload: - DaysAfterInitiation: 1 - Tags: - - Key: 'cloudspout:environment' - Value: !Ref environment - - Key: 'cloudspout:project' - Value: 'nas-backup' - - Key: 'cloudspout:repository' - Value: 'cloudspout/nas-backup' - - # IAM Service User - NASBackupUser: - Type: 'AWS::IAM::User' - Properties: - UserName: !Sub 'nas-backup-${environment}' - Tags: - - Key: 'cloudspout:environment' - Value: !Ref environment - - Key: 'cloudspout:project' - Value: 'nas-backup' - - Key: 'cloudspout:repository' - Value: 'cloudspout/nas-backup' - - # IAM Managed Policy - NASBackupManagedPolicy: - Type: 'AWS::IAM::ManagedPolicy' - Properties: - ManagedPolicyName: !Sub 'nas-backup-policy-${environment}' - Users: - - !Ref NASBackupUser - PolicyDocument: - Version: '2012-10-17' - Statement: - # 1. Account-level Global Access - - Effect: Allow - Action: - - 's3:ListAllMyBuckets' - Resource: '*' - - # 2. Bucket-level Access (Listing and Configuration) - - Effect: Allow - Action: - - 's3:ListBucket' - - 's3:GetBucketLocation' - - 's3:ListBucketMultipartUploads' # Required for resuming failed large uploads - Resource: - - !GetAtt NASBackupBucket.Arn - - # 3. Object-level Access (Data movement) - - Effect: Allow - Action: - - 's3:PutObject' - - 's3:GetObject' - - 's3:DeleteObject' - - 's3:AbortMultipartUpload' # Required to cancel failed chunks - - 's3:ListMultipartUploadParts' # Required to verify uploaded chunks - Resource: - - !Sub '${NASBackupBucket.Arn}/*' - - # Access Key for the NAS - NASBackupUserAccessKey: - Type: 'AWS::IAM::AccessKey' - Properties: - UserName: !Ref NASBackupUser - -Outputs: - BucketName: - Value: !Ref NASBackupBucket - IAMUserName: - Value: !Ref NASBackupUser - AccessKeyId: - Value: !Ref NASBackupUserAccessKey - SecretAccessKey: - Value: !GetAtt NASBackupUserAccessKey.SecretAccessKey