diff --git a/cisco/asav/Makefile b/cisco/asav/Makefile index 5e9fc8cc..efc65c69 100644 --- a/cisco/asav/Makefile +++ b/cisco/asav/Makefile @@ -9,4 +9,3 @@ VERSION=$(shell echo $(IMAGE) | sed -e 's/.\+[^0-9]\([0-9]\+\-[0-9]\+\-[0-9]\+[a -include ../../makefile-sanity.include -include ../../makefile.include --include ../../makefile-install.include \ No newline at end of file diff --git a/cisco/asav/README.md b/cisco/asav/README.md index 073c7c84..00eba4de 100644 --- a/cisco/asav/README.md +++ b/cisco/asav/README.md @@ -8,21 +8,34 @@ Put the .qcow2 file in this directory and run `make docker-image` and you should be good to go. The resulting image is called `vr-asav`. You can tag it with something else if you want, like `my-repo.example.com/vr-asav` and then push it to your repo. The tag is the same as the version of the ASAv image, so -if you have asav9-18-2.qcow2 your final docker image will be called -vr-asav:9-18-2 +if you have asav9-23-1.qcow2 your final docker image will be called +vr-asav:9-23-1. Please note that you will always need to specify version when starting your router as the "latest" tag is not added to any images since it has no meaning in this context. -It's been tested to boot and respond to SSH with: +It's been tested to boot and respond to SSH/telnet with: - * 9.18.2 (asav9-18-2.qcow2) + * 9.23.1 (asav9-23-1.qcow2) Usage ----- ``` -docker run -d --privileged --name my-asav-firewall vr-asav +# Start a container with the ASAv image. This can take 5-10 minutes to boot +docker run -d --privileged --name my-asav-firewall vrnetlab/cisco_asav:9-23-1 + +# Get the docker container's IP address +docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' my-asav-firewall + +# Follow the boot process, including SSH configuration, this may take a while +docker logs -f my-asav-firewall + +# After the ASAv has booted, SSH to it using the configured credentials +ssh vrnetlab@ # password: VR-netlab9 + +# Alternatively, you can connect to the console with telnet +telnet 5000 ``` Interface mapping @@ -52,4 +65,4 @@ Disk: <500MB FUAQ - Frequently or Unfrequently Asked Questions ------------------------------------------------- ##### Q: Has this been extensively tested? -A: Nope. +A: Nope. diff --git a/cisco/asav/docker/Dockerfile b/cisco/asav/docker/Dockerfile index 720080c4..060368a6 100644 --- a/cisco/asav/docker/Dockerfile +++ b/cisco/asav/docker/Dockerfile @@ -1,30 +1,5 @@ -FROM ubuntu:20.04 -MAINTAINER Kristian Larsson +FROM ghcr.io/srl-labs/vrnetlab-base:0.2.1 -ENV DEBIAN_FRONTEND=noninteractive - -RUN apt-get update -qy \ - && apt-get upgrade -qy \ - && apt-get install -y \ - bridge-utils \ - iproute2 \ - python3-ipy \ - socat \ - qemu-kvm \ - tcpdump \ - ssh \ - inetutils-ping \ - dnsutils \ - telnet \ - genisoimage \ - && rm -rf /var/lib/apt/lists/* - -ARG VERSION -ENV VERSION=${VERSION} ARG IMAGE COPY $IMAGE* / COPY *.py / - -EXPOSE 22 161/udp 830 5000 10000-10099 -HEALTHCHECK CMD ["/healthcheck.py"] -ENTRYPOINT ["/launch.py"] diff --git a/cisco/asav/docker/launch.py b/cisco/asav/docker/launch.py old mode 100644 new mode 100755 index 309becfc..27c63399 --- a/cisco/asav/docker/launch.py +++ b/cisco/asav/docker/launch.py @@ -37,15 +37,16 @@ def trace(self, message, *args, **kws): class ASAv_vm(vrnetlab.VM): - def __init__(self, username, password, install_mode=False): + def __init__(self, username, password, conn_mode, install_mode=False): for e in os.listdir("/"): if re.search(".qcow2$", e): disk_image = "/" + e super(ASAv_vm, self).__init__( - username, password, disk_image=disk_image, ram=2048 + username, password, disk_image=disk_image, ram=2048, cpu="Nehalem", use_scrapli=True ) self.nic_type = "e1000" + self.conn_mode = conn_mode self.install_mode = install_mode self.num_nics = 8 @@ -58,7 +59,7 @@ def bootstrap_spin(self): self.start() return - (ridx, match, res) = self.tn.expect([b"ciscoasa>"], 1) + (ridx, match, res) = self.con_expect([b"ciscoasa>"], 1) if match: # got a match! if ridx == 0: # login if self.install_mode: @@ -73,12 +74,11 @@ def bootstrap_spin(self): self.wait_write("", wait=None) # run main config! - self.bootstrap_config() - # close telnet connection - self.tn.close() + self.apply_config() + # startup time? startup_time = datetime.datetime.now() - self.start_time - self.logger.info("Startup complete in: %s" % startup_time) + self.logger.debug("Startup complete in: %s" % startup_time) # mark as running self.running = True return @@ -94,54 +94,85 @@ def bootstrap_spin(self): return - def bootstrap_config(self): - """Do the actual bootstrap config""" - self.logger.info("applying bootstrap configuration") - self.wait_write("", None) + def apply_config(self): + """Apply the full configuration""" + self.logger.debug("Applying bootstrap configuration") self.wait_write("enable", wait="ciscoasa>") self.wait_write("VR-netlab9", wait="Enter Password:") self.wait_write("VR-netlab9", wait="Repeat Password:") self.wait_write("", wait="ciscoasa#") - self.wait_write("configure terminal", wait="#") - self.wait_write("N", wait="[Y]es, [N]o, [A]sk later:") - self.wait_write("", wait="(config)#") - self.wait_write("aaa authentication ssh console LOCAL") + self.wait_write("configure terminal", wait="ciscoasa#") + + # Handle the initial user prompt that appears after configure terminal + # The ASA will show a call-home prompt that we need to respond to + self.logger.debug("Handling initial user prompt") + # Give the prompt time to appear + time.sleep(2) + # Send 'N' followed by extra carriage return to get back to prompt + self.scrapli_tn.channel.write("N\r") + # Wait for the response message to complete + time.sleep(2) + self.scrapli_tn.channel.write("\r") + # Wait for prompt to appear + time.sleep(1) + # Read and discard any buffered output to clear the channel + _ = self.scrapli_tn.channel.read() + + # Now we should be at config prompt, send first command without waiting + self.logger.debug("Setting device access") + self.wait_write("aaa authentication ssh console LOCAL", wait=None) self.wait_write("aaa authentication enable console LOCAL") - self.wait_write( - "username %s password %s privilege 15" % (self.username, self.password) - ) + self.wait_write(f"username {self.username} password {self.password} privilege 15") + + self.logger.debug("Configuring management interface") self.wait_write("interface Management0/0") self.wait_write("nameif management") + self.wait_write("security-level 100") self.wait_write("ip address 10.0.0.15 255.255.255.0") self.wait_write("no shutdown") - self.wait_write("ssh 0.0.0.0 0.0.0.0 management") - self.wait_write("ssh version 2") + self.wait_write("exit") + + self.logger.debug("Adding default route") + self.wait_write("route management 0.0.0.0 0.0.0.0 10.0.0.2 1") + + self.logger.debug("Configuring management access") + self.wait_write("access-list MGMT_IN extended permit tcp any any eq ssh") + self.wait_write("access-group MGMT_IN in interface management") + + self.logger.debug("Configuring SSH") + self.wait_write("crypto key generate ecdsa elliptic-curve 256") self.wait_write("ssh key-exchange group dh-group14-sha256") - self.wait_write("crypto key generate ecdsa") - self.wait_write("write") + self.wait_write("ssh 0.0.0.0 0.0.0.0 management") + self.wait_write("no ssh stricthostkeycheck") + self.wait_write("ssh timeout 60") + + self.logger.debug("Saving configuration") + self.wait_write("write memory") self.wait_write("end") self.wait_write("\r", None) + self.logger.debug("Closing telnet connection") + self.scrapli_tn.close() + class ASAv(vrnetlab.VR): - def __init__(self, username, password): + def __init__(self, username, password, conn_mode): super(ASAv, self).__init__(username, password) - self.vms = [ASAv_vm(username, password)] + self.vms = [ASAv_vm(username, password, conn_mode)] class ASAv_installer(ASAv): """ASAv installer""" - def __init__(self, username, password): - super(ASAv, self).__init__(username, password) - self.vms = [ASAv_vm(username, password, install_mode=True)] + def __init__(self, username, password, conn_mode): + super(ASAv_installer, self).__init__(username, password, conn_mode) + self.vms = [ASAv_vm(username, password, conn_mode, install_mode=True)] def install(self): self.logger.info("Installing ASAv") asav = self.vms[0] while not asav.running: asav.work() - time.sleep(30) asav.stop() self.logger.info("Installation complete") @@ -156,6 +187,11 @@ def install(self): parser.add_argument("--username", default="vrnetlab", help="Username") parser.add_argument("--password", default="VR-netlab9", help="Password") parser.add_argument("--install", action="store_true", help="Install ASAv") + parser.add_argument( + "--connection-mode", + default="vrxcon", + help="Connection mode to use in the datapath" + ) args = parser.parse_args() LOG_FORMAT = "%(asctime)s: %(module)-10s %(levelname)-8s %(message)s" @@ -167,8 +203,8 @@ def install(self): logger.setLevel(1) if args.install: - vr = ASAv_installer(args.username, args.password) + vr = ASAv_installer(args.username, args.password, args.connection_mode) vr.install() else: - vr = ASAv(args.username, args.password) + vr = ASAv(args.username, args.password, args.connection_mode) vr.start()