Overview
At the end you have one firewall in AWS with a public IP. You manage it with the Web Interface and SSH, only from your own IP address. It does not forward any traffic yet, and it runs without a license until you install one.
The work in short:
- Convert the cOS Core 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 the firewall from the AMI as an EC2 instance (a virtual server in AWS).
- Log in with the EC2 Serial Console and set the password and management access.
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), the cOS Core image for x86_64, and an instance with 2 vCPU and 4 GB memory.
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 Core KVM image for x86_64 from MyClavister. The file name ends with -kvm-x86_64-bios-en.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 \ continues on the next line.
- Run all commands on your computer. Only the commands in step 12 are typed in the firewall CLI, where the prompt is Device:/>.
- 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 firewall.
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 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-core-15.00.07.05-kvm-x86_64-bios-en
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. Continue: put-role-policy still gives the role access to your bucket.
Step 6 - Import the image
If you created the vmimport role just now, wait one minute first. A new role 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, the new role 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, it stops with Max attempts exceeded, but 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 Core. 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. The firewall 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 the firewall takes over this role.
This is safe: until step 12 the firewall answers only inside its own subnet. In step 12 you change the password and limit management access before the firewall can be reached from the internet.
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-fw \
--description "Clavister firewall, all traffic allowed, the firewall 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}]"
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 instance type
Use c6i.large or c7i.large. Both have 2 vCPU, 4 GB memory and fixed CPU performance.
TYPE=c6i.large
cOS Core uses one of the two vCPUs to poll the network interfaces all the time (polling mode). For this reason CloudWatch shows about 50% CPU also without traffic. For other sizes, see How many vCPUs and how much memory do the different NetWall Virtual Appliances support?
Step 10 - Start the firewall
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-fw}]" \
--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.
The firewall does not use the AWS instance metadata service, so the command turns it off.
At the first start, the firewall can update its boot loader and restart one time. The first start takes about 1 minute.
Step 11 - Log in on the serial console
At the first start, the firewall has no default route. Step 12 adds it. Until then you log in with the EC2 Serial Console, which works like a console cable to the firewall.
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. Press Enter, then log in:
- Username: admin
- Password: admin
You now see the firewall CLI prompt Device:/>. This terminal window now shows the firewall console.
The console can show log lines about 169.254.169.254, the AWS metadata service. Step 10 turned that service off, because the firewall does not use it. You can ignore these lines.
You can also use the serial console in the AWS Management Console: EC2, Instances, select the firewall, then Connect, EC2 serial console.
Step 12 - Secure management access
Type these commands in the firewall CLI (Device:/>).
Set a new admin password. Replace YourNewStrongPassword with your own password. Use at least 15 characters and mix upper case, lower case, digits and special characters. Do not use double quotes in the password.
cc LocalUserDatabase AdminUsers
set User admin Password="YourNewStrongPassword"
cc
cc LocalUserDatabase AdminUsers opens the list of admin users. cc alone goes back.
The security group lets everything through, so the firewall decides who can manage it. Allow management only from your own public IP from step 11. HTTP_If1 controls both HTTP and HTTPS to the Web Interface, and SSH_If1 controls SSH.
Replace 203.0.113.10 with your own IP. With the example IP, you cannot reach the firewall over the internet and must log in with the serial console (step 11) again.
add Address IP4Address mgmt_admin Address=203.0.113.10
set RemoteManagement RemoteMgmtHTTP HTTP_If1 Network=mgmt_admin
set RemoteManagement RemoteMgmtSSH SSH_If1 Network=mgmt_admin
You can also use a network, for example Address=203.0.113.0/24.
Add a default route, so the firewall can reach the internet and you can reach the firewall:
set Interface Ethernet If1 AutoDefaultGatewayRoute=Yes
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, the firewall 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.
Check the routes with routes. You should see a 0.0.0.0/0 route.
Log out with exit. You see Username: again. Press Enter and type ~. (tilde, then dot) to close the connection. If you close the connection without exit, you stay logged in, and the next serial console connection does not ask for a password.
Step 13 - Open the Web Interface
On your computer, show the public IP of the firewall with echo $PUBLIC_IP.
From the computer with the IP you allowed in step 12, open https:// and the public IP in a browser, for example https://198.51.100.10.
The browser warns about the certificate, because the firewall uses a self-signed certificate at the start. Choose to continue to the site. Log in as admin with your new password.
You can also use the CLI over SSH. Answer yes if SSH asks about the host key.
ssh admin@$PUBLIC_IP
The firewall does not forward any traffic until you add IP policies. This guide does not cover that. See the cOS Core Administration Guide.
Step 14 - Install a license
Without a license, cOS Core runs in demo mode. For 2 hours after each start it works fully, except for subscription services. After that, only management access works.
Install the license as described in the section Installing a License of the cOS Core Getting Started Guide for KVM. In the Web Interface you upload the license under Status, Maintenance, License.
Good to know
Keep the same public IP. The public IP changes if you stop and start the instance. 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
The last command shows the new IP. Use it from now on.
If your own IP changes. You cannot manage the firewall from the new IP. Log in with the serial console (step 11) and type, with your new IP:
set Address IP4Address mgmt_admin Address=203.0.113.20
activate
commit
Route traffic for other servers. To route other servers in AWS through the firewall, you need more than this guide covers, for example a second interface and VPC route tables. One of the steps is to turn off the AWS source and destination check on the firewall:
aws ec2 modify-instance-attribute --instance-id $INSTANCE --no-source-dest-check
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-core-15.00.07.05-kvm-x86_64-bios-en
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-fw \
--query "SecurityGroups[0].GroupId" --output text)
INSTANCE=$(aws ec2 describe-instances \
--filters Name=tag:Name,Values=clavister-fw 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 if that part does not exist yet, for example ALLOC if you have no Elastic IP. To start one more firewall from the same AMI, go to step 9.
Remove everything
This deletes the firewall 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 firewall, terminate the others first.
aws ec2 terminate-instances --instance-ids $INSTANCE
aws ec2 wait instance-terminated --instance-ids $INSTANCE
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
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
28 Oct, 2025 core proxmox kvm
26 May, 2021 kvm core arm x86
24 Nov, 2021 core arm kvm
20 May, 2021 kvm core arm coscore netwall
9 Apr, 2025 arm x86 core kvm vmware hyperv