Overview
At the end you have one NetShield in AWS with a public IP. You manage it with SSH, only from your own IP address. It does not forward any traffic yet, and it runs in demo mode until you install a license.
The work in short:
- Convert the cOS Stream image and upload it to S3 (AWS file storage).
- Import the file as a snapshot (a copy of a disk) with AWS VM Import.
- Make an AMI from the snapshot. An AMI is a template that you start virtual servers from.
- Start NetShield from the AMI as an EC2 instance (a virtual server in AWS).
- Set up the network and SSH access in the EC2 Serial Console.
Time: about 30 minutes. About 10 minutes of it is waiting for the image import.
Cost: from step 10, AWS charges per hour for the instance and its public IP. The S3 file and the snapshot cost a little. The section Remove everything at the end deletes it all.
The example uses AWS region eu-north-1 (Stockholm) and the cOS Stream image for x86_64. Step 9 explains how to choose the number of vCPU.
Requirements
- An AWS account. If you created the account yourself, or your user has the AdministratorAccess policy, you have all the rights you need.
- AWS CLI version 2.32.0 or later, installed as described in the AWS CLI install guide. Check the version with aws --version.
- qemu-img to convert the image:
- Debian or Ubuntu: sudo apt install qemu-utils
- RHEL or Fedora: sudo dnf install qemu-img
- macOS: brew install qemu
- The cOS Stream image for KVM from MyClavister. The file name ends with -virtual-x64-generic.qcow2.
- An SSH client.
The commands are for a Linux or macOS terminal. On Windows, install WSL with wsl --install and run the commands in WSL. Your Windows Downloads folder is /mnt/c/Users/<your Windows user>/Downloads in WSL.
How to use the commands
- Copy each command block as a whole. A line that ends with a backslash (\) continues on the next line.
- On a phone, swipe a command block sideways to see the end of long lines.
- Run all commands on your computer. Only the commands in step 12 are typed in the NetShield CLI, where the prompt is System:/>.
- A line like BUCKET=... saves a value in a shell variable. Later commands use it as $BUCKET. Variables only exist in the terminal window where you set them, so use the same window for all steps. If you close it, see If you closed the terminal at the end.
- Check a variable with echo, for example echo $BUCKET. If it shows None or nothing, the value is not set. Run the command before it again and check its output before you go on.
How to set this up
Steps 1 to 8 prepare the AMI and the security group. You do them one time. Steps 9 to 14 start and set up one NetShield.
Step 1 - Log in to AWS
aws login
Your browser opens. Log in the same way as to the AWS Management Console, then go back to the terminal. If the command asks for a region, type eu-north-1.
If the computer has no browser, use this command instead:
aws login --remote
It shows a URL. Open the URL on another device, log in, and paste the code you get into the terminal.
If you log in as an IAM user (not the root user), the user needs the AWS managed policy SignInLocalDevelopmentAccess. If your company uses AWS IAM Identity Center (single sign-on), ask your AWS admin how to log in with the CLI.
Check the login:
aws sts get-caller-identity
You see your 12-digit account number after "Account". If you do not see it, run aws login again.
The login is valid for up to 12 hours. After that, AWS commands ask you to log in again: run aws login.
Step 2 - Set variables
Go to the folder with the image file. Show the exact file name with ls *.qcow2.
In the block below, replace the IMAGE value with your file name, without .qcow2. Then run it.
export AWS_REGION=eu-north-1
IMAGE=clavister-cos-stream-4.10.02.10-virtual-x64-generic
BUCKET=clavister-import-$(aws sts get-caller-identity --query Account --output text)
echo $AWS_REGION $IMAGE $BUCKET
The last line shows all three values. What the lines do:
- AWS_REGION tells every AWS command in this terminal to use the region eu-north-1.
- IMAGE saves the image name, so later commands can use $IMAGE.
- BUCKET is the name of the S3 bucket (a storage folder) for the upload. $(...) runs a command and uses its output. Here it gets your account number, so the bucket name becomes for example clavister-import-123456789012. Bucket names must be unique in all of AWS, and the account number makes it unique.
Step 3 - Convert the image to RAW
AWS imports RAW disk images, so convert the qcow2 image to RAW:
qemu-img convert -O raw $IMAGE.qcow2 $IMAGE.raw
The command prints nothing when it works. You get a new 1 GB file that ends with .raw.
Step 4 - Upload the image to S3
aws s3 mb s3://$BUCKET
aws s3 cp $IMAGE.raw s3://$BUCKET/
The first command makes the bucket and prints make_bucket: and the name. The second command uploads the file and shows the progress.
Step 5 - Create the vmimport role
VM Import is the AWS service that turns your file into a snapshot. It works with a role called vmimport. A role is a set of rights that an AWS service can use.
Create two policy files. Copy the whole block, from the first cat to the last EOF. It prints nothing.
cat > trust-policy.json <<EOF
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": { "Service": "vmie.amazonaws.com" },
"Action": "sts:AssumeRole",
"Condition": { "StringEquals": { "sts:Externalid": "vmimport" } }
}]
}
EOF
cat > role-policy.json <<EOF
{
"Version": "2012-10-17",
"Statement": [
{ "Effect": "Allow",
"Action": ["s3:GetBucketLocation", "s3:GetObject", "s3:ListBucket"],
"Resource": ["arn:aws:s3:::$BUCKET", "arn:aws:s3:::$BUCKET/*"] },
{ "Effect": "Allow",
"Action": ["ec2:ModifySnapshotAttribute", "ec2:CopySnapshot",
"ec2:RegisterImage", "ec2:Describe*"],
"Resource": "*" }
]
}
EOF
Check that your bucket name is in the file with grep clavister-import role-policy.json.
Create the role and give it access to your bucket:
aws iam create-role --role-name vmimport \
--assume-role-policy-document file://trust-policy.json
aws iam put-role-policy --role-name vmimport --policy-name clavister-image-import \
--policy-document file://role-policy.json
If your account already has the vmimport role from an earlier import, create-role answers EntityAlreadyExists. That is fine, the role is already there. put-role-policy still gives it access to your bucket.
Step 6 - Import the image
Wait one minute after step 5 before you start the import. A new role or policy takes a short time to become active.
TASK=$(aws ec2 import-snapshot --description "$IMAGE" \
--disk-container "Format=RAW,UserBucket={S3Bucket=$BUCKET,S3Key=$IMAGE.raw}" \
--query ImportTaskId --output text)
echo $TASK
You should see an ID that starts with import-snap-. If the command answers that the role is not found or has no access to the bucket (s3:GetObject), the new policy is not active yet. Wait one minute and run the block again.
Wait for the import:
aws ec2 wait snapshot-imported --import-task-ids $TASK
The terminal shows nothing for about 10 minutes. Keep it open. The wait command checks for up to 10 minutes. If the import takes longer, wait stops with the text Max attempts exceeded. This is only the time limit of wait, the import goes on in AWS. Run the same wait command again.
Get the snapshot ID:
SNAPSHOT=$(aws ec2 describe-import-snapshot-tasks --import-task-ids $TASK \
--query "ImportSnapshotTasks[0].SnapshotTaskDetail.SnapshotId" --output text)
echo $SNAPSHOT
You should see an ID that starts with snap-. If you see None, the import is not finished. Check its status with:
aws ec2 describe-import-snapshot-tasks --import-task-ids $TASK
Step 7 - Create the AMI
These settings tell AWS how to start cOS Stream. Use them as they are.
AMI=$(aws ec2 register-image --name $IMAGE \
--architecture x86_64 --boot-mode legacy-bios --ena-support \
--virtualization-type hvm --root-device-name /dev/xvda \
--block-device-mappings "DeviceName=/dev/xvda,Ebs={SnapshotId=$SNAPSHOT,VolumeType=gp3}" \
--query ImageId --output text)
aws ec2 wait image-available --image-ids $AMI
echo $AMI
You should see an ID that starts with ami-.
The file in S3 is not needed any more. Delete it:
aws s3 rm s3://$BUCKET/$IMAGE.raw
Step 8 - Create the security group
A security group is the AWS packet filter in front of the instance. NetShield does the filtering. If the security group also filters, you must keep two sets of rules in sync. So this security group allows all traffic in and out, and NetShield takes over this role.
This is safe: until step 12, NetShield has no IP settings yet and does not answer any traffic. In step 12 you set the password and limit management access before NetShield can be reached.
This guide uses the default VPC (the default network in your AWS account).
VPC=$(aws ec2 describe-vpcs --filters Name=isDefault,Values=true \
--query "Vpcs[0].VpcId" --output text)
SG=$(aws ec2 create-security-group --group-name clavister-netshield \
--description "Clavister NetShield, all traffic allowed, NetShield filters" \
--vpc-id $VPC --query GroupId --output text)
aws ec2 authorize-security-group-ingress --group-id $SG \
--ip-permissions "IpProtocol=-1,IpRanges=[{CidrIp=0.0.0.0/0}]" --query Return --output text
echo $VPC $SG
You should see an ID that starts with vpc- and one that starts with sg-. All outbound traffic is allowed by default, so only inbound needs a rule.
If VPC is None, your account has no default VPC. Create one with aws ec2 create-default-vpc and run this step again.
Step 9 - Choose the number of vCPU
NetShield needs at least 2 vCPU. On 2 vCPU, the control plane (management, routing, logging) and the data plane (the traffic) share the vCPU, so both vCPU show the role Data plane.
With more vCPU, NetShield sets up the vCPU by itself at each start. It first gives vCPU to its fixed roles: the control plane, a scheduler and the I/O threads that move packets to and from the network interface. These roles use the first 5 or 6 vCPU. Every vCPU above that becomes a worker that handles traffic, and more workers give more throughput.
This is how NetShield uses the vCPU on the c6i instance types:
| Instance type | vCPU | Memory | Workers for traffic |
|---|---|---|---|
| c6i.large | 2 | 4 GB | Shared, see above |
| c6i.2xlarge | 8 | 16 GB | 3 |
| c6i.4xlarge | 16 | 32 GB | 10 |
Use 2 vCPU for small setups and tests. For more traffic, use 8 vCPU or more. Larger sizes add more workers in the same way. You can change the size later, see Good to know.
Set the instance type you chose:
TYPE=c6i.large
Step 10 - Start NetShield
SUBNET=$(aws ec2 describe-subnets \
--filters Name=vpc-id,Values=$VPC Name=default-for-az,Values=true \
--query "Subnets[0].SubnetId" --output text)
INSTANCE=$(aws ec2 run-instances --image-id $AMI --instance-type $TYPE \
--subnet-id $SUBNET --security-group-ids $SG --associate-public-ip-address \
--metadata-options HttpEndpoint=disabled \
--tag-specifications "ResourceType=instance,Tags=[{Key=Name,Value=clavister-netshield}]" \
--query "Instances[0].InstanceId" --output text)
aws ec2 wait instance-running --instance-ids $INSTANCE
PUBLIC_IP=$(aws ec2 describe-instances --instance-ids $INSTANCE \
--query "Reservations[0].Instances[0].PublicIpAddress" --output text)
echo $INSTANCE $PUBLIC_IP
You should see the instance ID (i-...) and the public IP. Write them down. From now on AWS charges per hour for the instance.
NetShield does not use the AWS instance metadata service, so the command turns it off.
The first start takes about 2 minutes.
Step 11 - Open the serial console
NetShield starts without network settings. So the first setup uses the EC2 Serial Console, which works like a console cable to NetShield.
First find your own public IP address. You need it in step 12.
curl https://checkip.amazonaws.com
Allow the serial console in your account (only needed one time per region) and make an SSH key for it:
aws ec2 enable-serial-console-access
ssh-keygen -t ed25519 -f serial_key -N ""
Send the key to AWS and connect. Connect within 60 seconds after you send the key.
aws ec2-instance-connect send-serial-console-ssh-public-key --instance-id $INSTANCE \
--serial-port 0 --ssh-public-key file://serial_key.pub
ssh -i serial_key $INSTANCE.port0@serial-console.ec2-instance-connect.$AWS_REGION.aws
AWS allows one serial console session per instance. If a session is still open, AWS answers SerialConsoleSessionLimitExceededException. Wait 60 seconds and run both commands again.
Answer yes if SSH asks about the host key, then press Enter. The NetShield console does not ask for a login. You see the CLI prompt System:/>. This terminal window now shows the NetShield console.
You can also use the serial console in the AWS Management Console: EC2, Instances, select NetShield, then Connect, EC2 serial console.
Step 12 - Set up the network and SSH access
Type these commands in the NetShield CLI (System:/>).
Get the IP address from AWS with DHCP, and add a default route through the AWS gateway. DHCP fills in if1_gw by itself.
set EthernetInterface if1 DHCPEnabled=Yes
cc RoutingTable main
add Route Interface=if1 Network=all-nets-ip4 Gateway=if1_gw
cc
Set a new admin password. Replace YourNewStrongPassword with your own strong password. Do not use double quotes in the password.
cc LocalUserDatabase AdminUsers
set User admin Password="YourNewStrongPassword"
cc
Turn on SSH management, only from your own public IP from step 11. Replace 203.0.113.10 with your own IP.
add Address IPAddress mgmt_admin Address=203.0.113.10
set RemoteManagement RemoteMgmtSSH RemoteMgmtSSH -enable
set RemoteManagement RemoteMgmtSSH RemoteMgmtSSH SourceInterface=if1 SourceNetwork=mgmt_admin
You can also use a network, for example Address=203.0.113.0/24.
Save the changes with activate. Wait until the console shows this text:
Run the "commit" command to keep the new configuration.
Then type commit within 30 seconds. Without commit, NetShield goes back to the previous configuration by itself, which protects you if a change breaks your connection. If the 30 seconds pass without commit, type activate and commit again.
activate can show two lines that start with Config warning. Both are normal on a new NetShield in AWS: one says that there is no license yet (demo mode), and one is about all-multicast mode on if1, which AWS networks do not use. No action is needed.
The first setup needs one restart. Type shutdown. This restarts NetShield, the instance keeps running. NetShield is back after about 1 minute and the serial console stays connected.
Check that DHCP works with ifstat if1. IP Address must be the private IP of the instance and DHCP state must be BOUND.
Press Enter and type ~. (tilde, then dot) to close the serial console. The password you typed is visible in the console, so close the terminal window when you are done. The serial console itself has no password. Access to it is controlled by the rights in your AWS account.
Step 13 - Log in with SSH
From the computer with the IP you allowed in step 12:
ssh admin@$PUBLIC_IP
Answer yes if SSH asks about the host key, and log in as admin with your new password.
To see how NetShield uses the vCPU, type system -cpuinfo -verbose. The Role column shows each vCPU: control plane, scheduler, I/O, data plane or worker.
NetShield does not forward any traffic until you add IP rules. This guide does not cover that. See the cOS Stream Administration Guide.
Step 14 - Install a license
Without a license, NetShield runs in demo mode for 2 hours after each start. After that, only management access works.
The license is bound to the MAC address of if1. Show it with ifstat if1 in the NetShield CLI. The MAC address stays the same when you stop, start or resize the instance.
Register the license and install it as described in Register a License and Bind It in the Virtual cOS Stream Getting Started Guide. Choose NetShield and the product type Virtual Model. Upload the license file with SCP to the public IP, and activate it with license activate <license-filename>.
Good to know
Change the number of vCPU later. Stop the instance, change the type and start it again. NetShield splits the new vCPU by itself at the start. The private IP and the MAC address stay the same, so the license still fits.
aws ec2 stop-instances --instance-ids $INSTANCE
aws ec2 wait instance-stopped --instance-ids $INSTANCE
aws ec2 modify-instance-attribute --instance-id $INSTANCE \
--instance-type "{\"Value\": \"c6i.2xlarge\"}"
aws ec2 start-instances --instance-ids $INSTANCE
aws ec2 wait instance-running --instance-ids $INSTANCE
PUBLIC_IP=$(aws ec2 describe-instances --instance-ids $INSTANCE \
--query "Reservations[0].Instances[0].PublicIpAddress" --output text)
echo $PUBLIC_IP
The public IP changes when you stop and start the instance, unless you use an Elastic IP.
Keep the same public IP. To keep the same IP, use an Elastic IP. AWS charges for it while it exists.
ALLOC=$(aws ec2 allocate-address --domain vpc --query AllocationId --output text)
aws ec2 associate-address --instance-id $INSTANCE --allocation-id $ALLOC
PUBLIC_IP=$(aws ec2 describe-addresses --allocation-ids $ALLOC \
--query "Addresses[0].PublicIp" --output text)
echo $PUBLIC_IP
If your own IP changes. You cannot manage NetShield from the new IP. Open the serial console (step 11) and type, with your new IP:
set Address IPAddress mgmt_admin Address=203.0.113.20
activate
commit
If you closed the terminal
The variables are gone in a new terminal. Set them again. Use your own image name in IMAGE.
export AWS_REGION=eu-north-1
IMAGE=clavister-cos-stream-4.10.02.10-virtual-x64-generic
BUCKET=clavister-import-$(aws sts get-caller-identity --query Account --output text)
AMI=$(aws ec2 describe-images --owners self --filters Name=name,Values=$IMAGE \
--query "Images[0].ImageId" --output text)
SNAPSHOT=$(aws ec2 describe-images --image-ids $AMI \
--query "Images[0].BlockDeviceMappings[0].Ebs.SnapshotId" --output text)
VPC=$(aws ec2 describe-vpcs --filters Name=isDefault,Values=true \
--query "Vpcs[0].VpcId" --output text)
SG=$(aws ec2 describe-security-groups \
--filters Name=vpc-id,Values=$VPC Name=group-name,Values=clavister-netshield \
--query "SecurityGroups[0].GroupId" --output text)
INSTANCE=$(aws ec2 describe-instances \
--filters Name=tag:Name,Values=clavister-netshield Name=instance-state-name,Values=running,stopped \
--query "Reservations[0].Instances[0].InstanceId" --output text)
PUBLIC_IP=$(aws ec2 describe-instances --instance-ids $INSTANCE \
--query "Reservations[0].Instances[0].PublicIpAddress" --output text)
ALLOC=$(aws ec2 describe-addresses --filters Name=instance-id,Values=$INSTANCE \
--query "Addresses[0].AllocationId" --output text)
TYPE=c6i.large
echo $AMI $SNAPSHOT $SG $INSTANCE $PUBLIC_IP $ALLOC
A value shows None or is empty if that part does not exist yet, for example ALLOC if you have no Elastic IP. A line that uses such a value can then print a short AWS message, for example about an invalid ID. You can ignore it. To start one more NetShield from the same AMI, go to step 9.
Remove everything
This deletes NetShield and the AWS resources this guide created. It uses the variables from the steps above. In a new terminal, first run the block in If you closed the terminal.
If you started more than one NetShield, terminate the others first.
aws ec2 terminate-instances --instance-ids $INSTANCE
aws ec2 wait instance-terminated --instance-ids $INSTANCE
This takes about 5 minutes.
If you use an Elastic IP, release it now:
aws ec2 release-address --allocation-id $ALLOC
Then delete the rest:
aws ec2 delete-security-group --group-id $SG
aws ec2 deregister-image --image-id $AMI
aws ec2 delete-snapshot --snapshot-id $SNAPSHOT
aws s3 rb s3://$BUCKET --force
aws iam delete-role-policy --role-name vmimport --policy-name clavister-image-import
Some commands print a short output, for example True or a few lines of JSON. That is normal. If a command says that a resource does not exist, it is already removed. Continue with the next command.
The vmimport role stays in your account. Other imports can use it. To delete it, run aws iam delete-role --role-name vmimport, but only if nothing else uses the role.
Serial console access stays on in the region. To turn it off, run aws ec2 disable-serial-console-access, but only if nobody else in the account uses the serial console.
On your computer you can delete the files from this guide: the .qcow2 and .raw image files, serial_key, serial_key.pub, trust-policy.json and role-policy.json.
A terminated instance is still listed in EC2 for about one hour. That is normal and costs nothing.
Related articles
27 Mar, 2025 stream proxmox
28 Oct, 2025 core proxmox kvm
15 Oct, 2026 core aws kvm