Skip to main content

SageMaker Notebook Instance Should Be In VPC

More Info:

Sagemaker Notebook instance should be associated to a VPC

Risk Level

Medium

Address

Security

Compliance Standards

  • APRA CPS 234 (Australia)
  • BSI C5 (Germany)
  • Brazil LGPD
  • CCPA / CPRA (California)
  • CIS Critical Security Controls v8
  • CMMC 2.0
  • CSA Cloud Controls Matrix v4
  • DPDPA
  • Digital Operational Resilience Act (EU)
  • ISO 27001
  • ISO/IEC 27018
  • ISO/IEC 27701
  • MAS Technology Risk Management (Singapore)
  • MITRE ATT&CK (Cloud)
  • NIST SP 800-171
  • NYDFS 23 NYCRR 500
  • SOC2
  • SWIFT Customer Security Controls Framework
  • Sarbanes-Oxley IT General Controls
  • UK NCSC Cyber Assessment Framework

Triage and Remediation

Remediation

Using Console

For SageMaker Notebook Instances, you cannot change an existing notebook from “no VPC” to “in VPC.” You must create a new notebook instance in a VPC and then decommission the old one.

Below are step‑by‑step instructions using the AWS Console:


1. Prerequisites: Identify / Prepare VPC, Subnet, and Security Group

  1. In the AWS Console, go to VPC service.
  2. Choose (or create) a VPC you want to use.
  3. Under Subnets, pick a private subnet (recommended) with:
    • Route to required services (e.g., NAT Gateway for internet access if needed).
  4. Under Security Groups, either:
    • Choose an existing one, or
    • Create a new security group with:
      • Outbound rules to allow access to SageMaker endpoints, S3, etc. (usually “All traffic” outbound is fine if routed via NAT).
      • Inbound rules only if you need specific inbound connections (often none are needed for notebooks).

2. Create a New Notebook Instance in the VPC

  1. Go to Amazon SageMaker in the console.
  2. In the left navigation pane, select Notebook instances.
  3. Click Create notebook instance.
  4. Fill in:
    • Notebook instance name: e.g., sagemaker-notebook-in-vpc.
    • Notebook instance type: same as your current instance or as needed.
  5. IAM role:
    • Select Existing role if you already have a SageMaker execution role.
    • Otherwise, choose Create a new role and allow required S3 access.
  6. Scroll down to Network (or Network and security) section:
    • VPC: Select your target VPC.
    • Subnet: Select the private subnet you prepared.
    • Security group(s): Select the security group you prepared.
  7. (Optional) Under Additional configuration:
    • Attach the same Lifecycle configuration as the old instance if you used one.
    • Configure any volume size, encryption, etc., to match your requirements.
  8. Click Create notebook instance.
  9. Wait until the status becomes InService.

3. Migrate Your Work (Notebooks, Code, Data)

  1. Open the old notebook instance:
    • From Notebook instances, click Open Jupyter or Open JupyterLab.
  2. Copy notebooks and files to durable storage:
    • Preferred: Save them in Amazon S3 (if they’re not already).
      • Use the Jupyter file browser “Download” or upload them to S3 using the AWS CLI / SDK / Jupyter extensions.
  3. Open the new VPC‑based notebook instance:
    • From Notebook instances, click Open Jupyter or Open JupyterLab.
  4. Restore content:
    • Download from S3 or upload from local machine into the new notebook instance.
  5. Verify:
    • Run a few notebooks to confirm they can access needed data (S3, databases, external APIs via NAT, etc.).

4. Decommission the Old Non‑VPC Notebook Instance

  1. In Amazon SageMakerNotebook instances:
    • Select the old notebook instance.
  2. Choose Stop.
  3. Wait until the status is Stopped.
  4. With the instance selected, choose Delete.
  5. Confirm deletion.

5. (Optional) Tighten Network Controls

In VPC → Security groups:

  • Restrict inbound rules to only what is necessary.
  • Leave outbound open or restrict to required destinations if you have more advanced network controls (VPC endpoints, proxy, etc.).

This process results in your SageMaker Notebook Instance running inside a VPC, satisfying the “Notebook Instance Should Be In VPC” requirement.

Using CLI

For SageMaker, you cannot “move” an existing Notebook Instance into a VPC; you must create a new notebook in a VPC, move any code/data, then delete the old one.

Below are step‑by‑step AWS CLI–based instructions.


1. Identify notebook instances not in a VPC

aws sagemaker list-notebook-instances \
--query 'NotebookInstances[*].NotebookInstanceName' \
--output text

For each notebook:

NB_NAME="your-notebook-name"

aws sagemaker describe-notebook-instance \
--notebook-instance-name "$NB_NAME" \
--query '{Name:NotebookInstanceName,SubnetId:SubnetId,SecurityGroups:SecurityGroups}' \
--output json

Any instance with "SubnetId": null is not in a VPC.


2. Choose / prepare VPC resources

You need:

  • A VPC ID
  • A private subnet ID in that VPC
  • One or more security group IDs allowing needed outbound traffic (and inbound if required, e.g., for SSM or Git)

Get VPCs:

aws ec2 describe-vpcs \
--query 'Vpcs[*].{VpcId:VpcId,CidrBlock:CidrBlock}' \
--output table

Get subnets for a chosen VPC:

VPC_ID="vpc-xxxxxxxx"

aws ec2 describe-subnets \
--filters "Name=vpc-id,Values=$VPC_ID" \
--query 'Subnets[*].{SubnetId:SubnetId,Az:AvailabilityZone,Name:Tags[?Key==`Name`]|[0].Value}' \
--output table

Get or create a security group:

# List existing SGs in VPC
aws ec2 describe-security-groups \
--filters "Name=vpc-id,Values=$VPC_ID" \
--query 'SecurityGroups[*].{GroupId:GroupId,GroupName:GroupName}' \
--output table

Example: create a new SG:

SG_ID=$(aws ec2 create-security-group \
--group-name sagemaker-notebook-sg \
--description "SageMaker Notebook SG" \
--vpc-id "$VPC_ID" \
--query 'GroupId' \
--output text)

# (Optional) allow outbound to anywhere
aws ec2 authorize-security-group-egress \
--group-id "$SG_ID" \
--ip-permissions '[
{
"IpProtocol": "-1",
"IpRanges": [{"CidrIp": "0.0.0.0/0"}]
}
]'

3. Create a new Notebook Instance in the VPC

Decide:

  • INSTANCE_TYPE (e.g., ml.t3.medium)
  • ROLE_ARN – execution role with sagemaker:* / S3 permissions
  • SUBNET_ID – from step 2
  • SG_ID – from step 2
  • Optional: lifecycle config, volume size, etc.
NB_NEW_NAME="your-new-notebook-name"
INSTANCE_TYPE="ml.t3.medium"
ROLE_ARN="arn:aws:iam::123456789012:role/SageMakerExecutionRole"
SUBNET_ID="subnet-xxxxxxxx"
SG_ID="sg-xxxxxxxx"

aws sagemaker create-notebook-instance \
--notebook-instance-name "$NB_NEW_NAME" \
--instance-type "$INSTANCE_TYPE" \
--role-arn "$ROLE_ARN" \
--subnet-id "$SUBNET_ID" \
--security-group-ids "$SG_ID"

Wait for it to be InService:

aws sagemaker wait notebook-instance-in-service \
--notebook-instance-name "$NB_NEW_NAME"

Confirm VPC settings:

aws sagemaker describe-notebook-instance \
--notebook-instance-name "$NB_NEW_NAME" \
--query '{Name:NotebookInstanceName,SubnetId:SubnetId,SecurityGroups:SecurityGroups}' \
--output json

4. Migrate notebooks / data

Typical pattern (if old notebook is still accessible via console):

  1. From the old notebook, ensure all notebooks and code are saved to S3 (e.g., ~/SageMaker auto‑syncs to S3 if configured).

  2. Alternatively, sync manually:

    aws s3 sync /home/ec2-user/SageMaker s3://your-bucket/path/
  3. On the new notebook instance, pull from S3:

    aws s3 sync s3://your-bucket/path/ /home/ec2-user/SageMaker

If you used Git in your old instance, simply re‑clone your repos in the new instance.


5. Stop and delete old non‑VPC notebook instance

OLD_NB_NAME="old-notebook-name"

aws sagemaker stop-notebook-instance \
--notebook-instance-name "$OLD_NB_NAME"

aws sagemaker wait notebook-instance-stopped \
--notebook-instance-name "$OLD_NB_NAME"

aws sagemaker delete-notebook-instance \
--notebook-instance-name "$OLD_NB_NAME"

6. (Optional) Enforce “must be in VPC” via SCP or IaC

To prevent future non‑VPC notebooks, you can:

  • Use a Service Control Policy denying sagemaker:CreateNotebookInstance if sagemaker:SubnetId is not present.
  • Use Infrastructure‑as‑Code (CloudFormation/Terraform) that always sets SubnetId and SecurityGroupIds.

If you share your current notebook config (from describe-notebook-instance), I can give an exact create-notebook-instance CLI command that reproduces it inside a VPC.

Using Python

For SageMaker, a notebook instance cannot be moved into a VPC after creation. You must:

  1. Create (or choose) a VPC, subnet, and security group.
  2. Create a new SageMaker notebook instance configured to use that VPC.
  3. Migrate code/data from the old notebook (e.g., via Git, S3).
  4. Delete the old non‑VPC notebook.

Below is the Python/boto3 approach.


1. Prerequisites

  • boto3 installed and configured with credentials/region.
  • You have (or will create) the following:
    • VPC ID: vpc-xxxxxxxx
    • Subnet ID in that VPC: subnet-xxxxxxxx
    • Security Group ID that allows needed traffic: sg-xxxxxxxx
    • IAM role with SageMaker permissions: arn:aws:iam::<account-id>:role/<SageMakerRole>

2. Create a SageMaker Notebook in a VPC (Python/boto3)

import boto3

sagemaker = boto3.client("sagemaker", region_name="us-east-1")

notebook_instance_name = "my-vpc-notebook"
role_arn = "arn:aws:iam::<account-id>:role/<SageMakerRole>"
subnet_id = "subnet-xxxxxxxx"
security_group_ids = ["sg-xxxxxxxx"]

response = sagemaker.create_notebook_instance(
NotebookInstanceName=notebook_instance_name,
InstanceType="ml.t3.medium", # choose appropriate instance type
RoleArn=role_arn,
SubnetId=subnet_id,
SecurityGroupIds=security_group_ids,
DirectInternetAccess="Disabled", # or "Enabled" if you require it
VolumeSizeInGB=10, # adjust as needed
Tags=[
{"Key": "Name", "Value": "my-vpc-notebook"},
{"Key": "Environment", "Value": "prod"},
],
)

print("CreateNotebookInstance response:", response)

To wait until it’s ready:

import time

def wait_for_notebook_inservice(name):
while True:
desc = sagemaker.describe_notebook_instance(NotebookInstanceName=name)
status = desc["NotebookInstanceStatus"]
print("Status:", status)
if status in ["InService", "Failed", "Stopped"]:
break
time.sleep(30)

wait_for_notebook_inservice(notebook_instance_name)

3. (Optional) Stop and Delete the Old Non‑VPC Notebook

old_notebook_name = "my-old-nonvpc-notebook"

# Stop (if running)
try:
sagemaker.stop_notebook_instance(NotebookInstanceName=old_notebook_name)
except sagemaker.exceptions.ResourceNotFound:
pass

wait_for_notebook_inservice(old_notebook_name) # will show "Stopped"

# Delete
sagemaker.delete_notebook_instance(NotebookInstanceName=old_notebook_name)

4. Notes for Compliance

  • Ensure SubnetId and SecurityGroupIds are always specified when calling create_notebook_instance.
  • Enforce via CI/CD or internal tooling so that notebooks are only created with VPC settings.
  • If needed, you can verify compliance by listing and checking:
paginator = sagemaker.get_paginator("list_notebook_instances")
for page in paginator.paginate():
for ni in page["NotebookInstances"]:
desc = sagemaker.describe_notebook_instance(
NotebookInstanceName=ni["NotebookInstanceName"]
)
if "SubnetId" not in desc:
print("Non‑VPC notebook:", ni["NotebookInstanceName"])
Using Terraform
resource "aws_sagemaker_notebook_instance" "THIS_NOTEBOOK" {
# Replace THIS_NOTEBOOK with the actual notebook name or keep as-is if already managed
name = "THIS_NOTEBOOK_NAME" # TODO: replace with your notebook name
instance_type = "ml.t3.medium" # TODO: keep your current instance type
role_arn = aws_iam_role.sagemaker_role.arn # TODO: keep your current IAM role

# VPC configuration – this is what fixes the finding
subnet_id = aws_subnet.NOTEBOOK_SUBNET.id # TODO: replace with your target private subnet

security_groups = [
aws_security_group.NOTEBOOK_SG.id # TODO: replace with one or more SGs that allow required access
]

# ...keep any other existing arguments (tags, lifecycle_config_name, etc.)
}

resource "aws_subnet" "NOTEBOOK_SUBNET" {
vpc_id = aws_vpc.MAIN.id # TODO: reference your VPC
cidr_block = "10.0.1.0/24" # TODO: match your network design
availability_zone = "us-east-1a" # TODO: match your AZ
map_public_ip_on_launch = false
# ...other subnet settings as needed
}

resource "aws_security_group" "NOTEBOOK_SG" {
name = "sagemaker-notebook-sg"
description = "Security group for SageMaker notebook in VPC"
vpc_id = aws_vpc.MAIN.id # TODO: reference your VPC

# TODO: tighten rules as appropriate; example for VPC‑internal access only:
ingress {
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["10.0.0.0/16"] # TODO: adjust to your internal CIDR
}

egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}

Changing an existing SageMaker Notebook Instance from “no VPC” to having subnet_id/security_groups forces resource replacement; expect downtime for this notebook during terraform apply.

Verification: terraform plan should show aws_sagemaker_notebook_instance.THIS_NOTEBOOK being replaced with a new instance that includes subnet_id and security_groups, and no other unrelated changes.