Compare commits

9 Commits

Author SHA1 Message Date
bed54826d6 Update README.md
move domain from deadbeef.codes to stevenpolley.net
2024-07-10 15:39:21 +00:00
b318bcb3c1 invert conditionals, removing crazy nesting
All checks were successful
continuous-integration/drone/push Build is passing
Thank you code aesthetic
2024-05-09 20:17:02 -06:00
390fabe1b4 remove pre-built eBPF programs
All checks were successful
continuous-integration/drone/push Build is passing
2024-04-25 21:37:15 -06:00
92f5c579e6 add README.md
All checks were successful
continuous-integration/drone/push Build is passing
2024-04-25 21:21:27 -06:00
305ba29c50 add openwrt kernel configuration instruction
All checks were successful
continuous-integration/drone/push Build is passing
2024-04-25 21:16:26 -06:00
f8be95c8d0 add fortinet integration example
All checks were successful
continuous-integration/drone/push Build is passing
2024-04-23 22:11:00 -06:00
0942fb132f QoL feature - select best interface on current system
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
When generating a default config instead of using a canned value like "eth0", hypd will isntead look at what interfaces the system has and make a best guess based on progressively narrowing filters.
2024-04-20 19:25:15 -06:00
6b1bfb3a01 Better usage in hyp and hypd readme's
All checks were successful
continuous-integration/drone/push Build is passing
2024-04-20 18:31:14 -06:00
2af574fd18 add optional refreshtime parameter to client
All checks were successful
continuous-integration/drone/push Build is passing
If refreshtime is specified, instead the client running as a one-shot command, it will instead run persistently and perform a new authentic knock sequence each specified time in minutes.
2024-04-20 17:23:52 -06:00
14 changed files with 258 additions and 49 deletions

4
.gitignore vendored
View File

@ -3,4 +3,6 @@ hyp.secret
hypd/hypd
hyp/hyp
hypd/hypdconfig.json
hypd/secrets/
hypd/secrets/
hypd/server/*.o
env.sh

View File

@ -1,6 +1,6 @@
# hyp | Hide Your Ports
[![Build Status](https://drone.deadbeef.codes/api/badges/steven/hyp/status.svg)](https://drone.deadbeef.codes/steven/hyp)
[![Build Status](https://drone.stevenpolley.net/api/badges/steven/hyp/status.svg)](https://drone.deadbeef.codes/steven/hyp)
hyp is a [port knocking](https://www.youtube.com/watch?v=a7VJZEJVhD0) implementation written in Go and C. hyp uses spread-spectrum UDP as an authentication mechanism and enables trusted agents to access services over the internet, wherever they are, and without the service being accessible by others. Your TCP and UDP ports are closed. They will not show in a port scan. Nobody else can connect to them. This is particularly useful as [there](https://nvd.nist.gov/vuln/detail/CVE-2024-21888) [have](https://nvd.nist.gov/vuln/detail/CVE-2023-20269) [been](https://nvd.nist.gov/vuln/detail/CVE-2021-26109) [a](https://nvd.nist.gov/vuln/detail/CVE-2024-22394) [few](https://nvd.nist.gov/vuln/detail/CVE-2024-21894) [VPN](https://nvd.nist.gov/vuln/detail/CVE-2024-3400) [gateway](https://nvd.nist.gov/vuln/detail/CVE-2023-27997) [vulnerabilities](https://nvd.nist.gov/vuln/detail/CVE-2024-21762) [over](https://nvd.nist.gov/vuln/detail/CVE-2022-3236) [the](https://nvd.nist.gov/vuln/detail/CVE-2024-21893) [years](https://nvd.nist.gov/vuln/detail/CVE-2022-42475). I often wonder what's out there and hasn't been discovered. Why take the chance of leaving your VPN open to the whole internet? With hyp, you don't have to.
@ -14,19 +14,21 @@ Compared to most port knocking daemons, hyp is extremely fast, lightweight and h
6. The knock daemon on the firewall verifies the sequence and performs the action of opening the firewall port configured for the client to let them in while remaining closed to everyone else.
7. The client connects to their application which has its own authentication, authorization, and auditing.
![Authentic Knock Sequence](https://deadbeef.codes/steven/hyp/raw/branch/main/docs/authentic-knock-sequence-calculation.png)
![Authentic Knock Sequence](https://code.stevenpolley.net/steven/hyp/raw/branch/main/docs/authentic-knock-sequence-calculation.png)
### Runtime Requirements
Port knocking clients have minimal requirements and can run on x86, ARM, MIPS, PowerPC, IBM390, or RISC-V. Currently only supported OS's are Linux and Windows, with support for Android planned to be added in the future.
The port knocking daemon has more strict requirements and is only available for Linux. It requires the kernel be built with CONFIG_DEBUG_INFO_BTF, which most major distributions have out of the box.
The port knocking daemon has more strict requirements and is only available for Linux. It requires the kernel be built with CONFIG_DEBUG_INFO_BTF, which most major distributions have out of the box. Additionally, hypd has some network requirements. hypd is only expected to work on ethernet networks with IPv4.
Once you get the runtime requirements sorted, be sure to check out the hyp and hypd directories of the repository for README information for how to use hyp.
### Build Requirements
Pre-built binaries for configurations I've tested are available on the [releases page](https://deadbeef.codes/steven/hyp/releases). This will likely run in many CPU architectures I haven't tested yet though.
To build this yourself, you will need Linux with packages for: git, clang, linux-headers-<architecture> libbpf-dev and golang. Check out the [Dockerfile ](https://deadbeef.codes/steven/hyp/src/branch/main/Dockerfile) as a reference for how the build environment for official releases is configured. Once the environment is ready, you can clone the repo and build.
To build this yourself, you will need Linux with packages for: git, clang, linux-headers-<architecture> libbpf-dev and golang. Check out the [Dockerfile](https://deadbeef.codes/steven/hyp/src/branch/main/Dockerfile) as a reference for how the build environment for official releases is configured. Once the environment is ready, you can clone the repo and build.
```sh
# Clone repository

View File

@ -1,9 +1,34 @@
# hyp-client
# hyp | Hide Your Ports Client
The client expects there to be a file named hyp.secret in the same directory. This file is generated by the hypd server using ./hypd generate secret.
The hyp client is used on machines to perform an authentic knock sequence.
### Usage
You can use -h to get help for hyp and all its commands. When figuring out how to do something, -h is your friend.
```bash
# Example Usage
# ./hyp knock <server>
./hyp knock 192.168.50.5
```
# Get general hyp help
./hyp -h
# Get help specific to the hyp knock command
./hyp knock -h
```
In order to use the hyp client, it must have the secret. Secrets are generated by hypd, the knock daemon. See the hypd README.md file for more information about generating secrets.
Once you have the secret, you can then perform an authentic knock sequence to a server.
```bash
# Assumes secret is in file named my-first-secret in same directory
./hyp knock 8.69.4.20 --secret my-first-secret
# If you omit --secret, hyp will look for a file named hyp.secret
./hyp knock 8.69.4.20
```
This will perform a single one-shot knock sequence and then hyp will exit. You can also run hyp in a persistent mode where it will perform an authentic knock sequence at a specified interval.
```bash
# Performs an authentic knock sequence every 45 minutes
./hyp knock 8.69.4.20 --refreshtime=45
```

View File

@ -47,6 +47,11 @@ Example usage:
panic(fmt.Errorf("maxjitter must be value between 1 and 1500"))
}
refreshTime, err := cmd.Flags().GetInt("refreshtime")
if err != nil {
panic(fmt.Errorf("failed to parse command flag 'refreshtime': %w", err))
}
secretBytes, err := os.ReadFile(secretFilePath)
if err != nil {
log.Fatalf("failed to read file 'hyp.secret': %v", err)
@ -57,18 +62,29 @@ Example usage:
log.Fatalf("failed to base32 decode secret '%s': %v", secretFilePath, err)
}
ports, err := otphyp.GeneratePorts(decodedSecret, time.Now())
if err != nil {
log.Fatalf("failed to generate ports from shared secret: %v", err)
}
for {
// Transmit
for _, port := range ports {
fmt.Printf("knock | %s:%d\n", args[0], port)
conn, _ := net.Dial("udp", fmt.Sprintf("%s:%d", args[0], port))
conn.Write([]byte{0})
conn.Close()
time.Sleep(time.Millisecond * time.Duration(maxJitter)) // TBD: Make this configurable with flag (maxJitter)
ports, err := otphyp.GeneratePorts(decodedSecret, time.Now())
if err != nil {
log.Fatalf("failed to generate ports from shared secret: %v", err)
}
// Transmit
for _, port := range ports {
fmt.Printf("knock | %s:%d\n", args[0], port)
conn, _ := net.Dial("udp", fmt.Sprintf("%s:%d", args[0], port))
conn.Write([]byte{0})
conn.Close()
time.Sleep(time.Millisecond * time.Duration(maxJitter)) // TBD: Make this configurable with flag (maxJitter)
}
if refreshTime < 1 {
break
}
sleepDuration := time.Minute * time.Duration(refreshTime)
fmt.Printf("waiting until: %s...\n", time.Now().Add(sleepDuration).Format("15:04:05"))
time.Sleep(sleepDuration)
}
},
}
@ -76,6 +92,7 @@ Example usage:
func init() {
rootCmd.AddCommand(knockCmd)
knockCmd.PersistentFlags().String("secret", "hyp.secret", "Path to the file containing the hyp secret.")
knockCmd.PersistentFlags().Int("maxjitter", 200, "Specifies the time in milliseconds between knock sequence transmissions.")
knockCmd.PersistentFlags().String("secret", "hyp.secret", "Path to the file containing the hyp secret")
knockCmd.PersistentFlags().Int("maxjitter", 200, "Specifies the time in milliseconds between transmissions while performing the knock sequence")
knockCmd.PersistentFlags().Int("refreshtime", 0, "If specified, the hyp client will run persistently and send a full knock sequence at this interval in minutes")
}

View File

@ -1,9 +1,19 @@
# hypd | Hide Your Ports Daemon
hypd is the pork knocking daemon which listens for incoming authentic knock sequences.
hypd is the pork knocking daemon which listens for incoming authentic knock sequences. When it sees an authentic knock sequence, it then performs an action.
### Usage
You can use -h to get help for hypd and all its commands. When figuring out how to do something, -h is your friend.
```bash
# Get general hypd help
./hypd -h
# Get help specific to the hypd generate command
./hypd generate -h
```
Running hypd requires generating secrets which are then shared with hyp clients. hypd is used to generate these secrets, and it's recommended you create a directory just for hyp secrets.
```bash
@ -23,11 +33,15 @@ Running hypd requires specifying a configuration file. It's recommended you gen
./hypd generate defaultconfig > hypd.conf
```
Make sure you take the time to review the hypd.conf file and edit it to your liking, this is the most important step.
Make sure you take the time to review the hypd.conf file and edit it to your liking, this is the most important step. Make sure the network interface is correct, hypd will make an educated guess based on the interfaces your system has.
If you have complex requirements, you can make the successAction/timeoutAction an external shell script. If you want to disable the timeoutAction, you can set timeoutSeconds to 0.
Once you have set your config file, you can finally run hypd.
```bash
# As root or sudo, specify the configuration file
sudo ./hypd server hypd.conf
```
```
If you encounter any errors while trying to run, address those. If not, then you're now listening for incoming authentic knock sequences. Make sure you distribute the secret to the client.

View File

@ -57,8 +57,16 @@ func LoadConfiguration(configFilePath string) (*HypdConfiguration, error) {
}
func DefaultConfig() *HypdConfiguration {
var ifaceString string
iface, err := getDefaultNIC()
if err == nil {
ifaceString = iface.Name
} else {
ifaceString = "enp0s3" // fallback to fixed value
}
return &HypdConfiguration{
NetworkInterface: "enp0s3",
NetworkInterface: ifaceString,
PreSharedKeyDirectory: "./secrets/",
SuccessAction: "iptables -A INPUT -p tcp -s %s --dport 22 -j ACCEPT",
TimeoutSeconds: 1440,

View File

@ -0,0 +1,81 @@
package configuration
import (
"fmt"
"net"
)
// QoL feature to try and detect the best NIC for hyp
func getDefaultNIC() (*net.Interface, error) {
ifaces, err := net.Interfaces()
if err != nil {
return nil, fmt.Errorf("failed to get network interfaces on this system: %v", err)
}
if len(ifaces) < 1 {
return nil, fmt.Errorf("this system has no network interfaces: %v", err)
}
// Just pick one to start
selectedIface := ifaces[0]
filteredIfaces := make([]net.Interface, 0)
// Check for ethernet addresses
for _, iface := range ifaces {
if len(iface.HardwareAddr) == 6 {
selectedIface = iface
filteredIfaces = append(filteredIfaces, iface)
}
}
ifaces = filteredIfaces
filteredIfaces = make([]net.Interface, 0)
// Check for interfaces that are up and not loopbacks
for _, iface := range ifaces {
if iface.Flags&net.FlagUp != 0 && iface.Flags&net.FlagRunning != 0 && iface.Flags&net.FlagLoopback == 0 {
selectedIface = iface
filteredIfaces = append(filteredIfaces, iface)
}
}
ifaces = filteredIfaces
filteredIfaces = make([]net.Interface, 0)
// Check for interfaces that have IPv4 addresses assigned
for _, iface := range ifaces {
addresses, err := iface.Addrs()
if err != nil {
continue
}
for _, address := range addresses {
ip, _, err := net.ParseCIDR(address.String())
if err != nil {
continue
}
if ip.To4() != nil {
selectedIface = iface
filteredIfaces = append(filteredIfaces, iface)
}
}
}
ifaces = filteredIfaces
filteredIfaces = nil
// Check for interfaces that have non RFC1918 addresses assigned
for _, iface := range ifaces {
addresses, err := iface.Addrs()
if err != nil {
continue
}
for _, address := range addresses {
ip, _, err := net.ParseCIDR(address.String())
if err != nil {
continue
}
if !ip.IsPrivate() {
selectedIface = iface
}
}
}
return &selectedIface, nil // TBD
}

View File

@ -0,0 +1,7 @@
{
"networkInterface": "enp0s3",
"preSharedKeyDirectory": "./secrets/",
"successAction": "./examples/fortigate/openfortigate.sh %s",
"timeoutSeconds": 0,
"timeoutAction": ""
}

View File

@ -0,0 +1,33 @@
#!/bin/bash
# Make sure you have environment variables set for FORTIGATE_MGMT_URL, FORTIGATE_API_TOKEN and FORTIGATE_ADDRESS_OBJECT_GROUP
# Examples:
export FORTIGATE_MGMT_URL="https://69.4.20.10:8443"
export FORTIGATE_API_KEY="5fkwkkzgQ4s31bdH60qsxxfN093zgt"
export FORTIGATE_ADDRESS_OBJECT_GROUP="hyp-allowed-clients"
if [ $# -lt 1 ]; then
echo "Usage: $0 <srcip>"
exit 1
fi
echo $FORTIGATE_MGMT_URL
echo $1
# Create the address object
curl "$FORTIGATE_MGMT_URL/api/v2/cmdb/firewall/address?datasource=1" \
-X "POST" \
-H "Authorization: Bearer $FORTIGATE_API_KEY" \
-H "Content-Type: application/json" \
--data-raw "{\"name\":\"hyp_$1\",\"subnet\":\"$1/32\",\"color\":\"0\"}" \
--insecure # LOL - remove this if you want, but I want this to be easy for noobs
# Add to address object group
curl "$FORTIGATE_MGMT_URL/api/v2/cmdb/firewall/addrgrp/$FORTIGATE_ADDRESS_OBJECT_GROUP/member" \
-X "POST" \
-H "Authorization: Bearer $FORTIGATE_API_KEY" \
-H "Content-Type: application/json" \
--data-raw "{\"name\":\"hyp_$1\"}" \
--insecure # And here too

View File

@ -0,0 +1,12 @@
# Using hyp with OpenWrt Wireguard
This example case is to deploy hypd on OpenWrt to open up access to the WireGuard VPN service.
hyp utilizes eBPF technology to ensure runtime overhead is extremely small (in a way, but in a way not). Most Linux distributions have support for this out of the box, however OpenWrt does not. OpenWrt has a very stripped down, purpose-configured kernel and does not have the requirements built in to run hyp.
The good news is, you can build OpenWrt yourself and configure it with the requirements. Follow the directions at this page: https://openwrt.org/docs/guide-developer/toolchain/use-buildsystem
When you run *make menuconfig*, make sure you check off *Enable additional BTF type information* which is also known as CONFIG_KERNEL_DEBUG_INFO_BTF. This is required to support eBPF CO:RE.
![Kernel Config](https://deadbeef.codes/steven/hyp/raw/branch/main/hypd/examples/openwrt-wireguard/kernel_config.png)

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 KiB

View File

@ -35,32 +35,40 @@ int xdp_prog_func(struct xdp_md *ctx) {
// A knock should not contain any data
if (data_end - data > 60) {
goto done;
return XDP_PASS;
}
// parse ethernet header
struct ethhdr *eth = data;
if ((void *)eth + sizeof(*eth) <= data_end) {
// parse IP header
struct iphdr *ip = data + sizeof(*eth);
if ((void *)ip + sizeof(*ip) <= data_end) {
if (ip->protocol == IPPROTO_UDP) {
// parse UDP header
struct udphdr *udp = (void *)ip + sizeof(*ip);
if ((void *)udp + sizeof(*udp) <= data_end)
{
// pack into knock structure and send to userspace
struct knock_data knock = {
.srcip = bpf_ntohl(ip->saddr),
.dstport = bpf_htons(udp->dest),
.pad = 0
};
bpf_ringbuf_output(&rb, &knock, sizeof(knock), BPF_RB_FORCE_WAKEUP);
}
}
}
if ((void *)eth + sizeof(*eth) > data_end) {
return XDP_PASS;
}
done:
// parse IP header
struct iphdr *ip = data + sizeof(*eth);
if ((void *)ip + sizeof(*ip) > data_end) {
return XDP_PASS;
}
// Ensure IP header protocol field is UDP (protocol 17)
if (ip->protocol != IPPROTO_UDP) {
return XDP_PASS;
}
// parse UDP header
struct udphdr *udp = (void *)ip + sizeof(*ip);
if ((void *)udp + sizeof(*udp) > data_end) {
return XDP_PASS;
}
// pack into knock structure and send to userspace
struct knock_data knock = {
.srcip = bpf_ntohl(ip->saddr),
.dstport = bpf_htons(udp->dest),
.pad = 0
};
bpf_ringbuf_output(&rb, &knock, sizeof(knock), BPF_RB_FORCE_WAKEUP);
// We send everything to XDP_PASS
return XDP_PASS;
}

Binary file not shown.

Binary file not shown.