diff --git a/.github/workflows/deploy-prod.yml b/.github/workflows/deploy-prod.yml index 7c3e21ef..69124795 100644 --- a/.github/workflows/deploy-prod.yml +++ b/.github/workflows/deploy-prod.yml @@ -5,7 +5,7 @@ on: # Triggers the workflow on tagged commits push: tags: - - '*.*.*' + - '[0-9]+.[0-9]+.[0-9]+' # A workflow run is made up of one or more jobs that can run sequentially or in parallel jobs: @@ -54,4 +54,4 @@ jobs: ANSIBLE_CONFIG: ./ansible.cfg - name: Clean Key - run: rm -rf ~/.privkey \ No newline at end of file + run: rm -rf ~/.privkey diff --git a/.github/workflows/release-develop.yml b/.github/workflows/release-develop.yml new file mode 100644 index 00000000..9617ff74 --- /dev/null +++ b/.github/workflows/release-develop.yml @@ -0,0 +1,30 @@ +name: Pre-Release for Haaukins + +# Controls when the action will run. +on: + # Triggers the workflow on tagged commits + push: + tags: + - '[0-9]+.[0-9]+.[0-9]+-pre' + +# A workflow run is made up of one or more jobs that can run sequentially or in parallel +jobs: + # This workflow contains a single job called "build" + build: + # The type of runner that the job will run on + runs-on: ubuntu-latest + + # Steps represent a sequence of tasks that will be executed as part of the job + steps: + # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it + - uses: actions/checkout@v2 + + - name: Run GoReleaser + uses: goreleaser/goreleaser-action@v2 + with: + version: latest + args: -f .goreleaser.prerelease.yml --rm-dist + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + diff --git a/.goreleaser.prerelease.yml b/.goreleaser.prerelease.yml new file mode 100644 index 00000000..814a1d66 --- /dev/null +++ b/.goreleaser.prerelease.yml @@ -0,0 +1,188 @@ +# .goreleaser.yml +before: + hooks: + - go mod tidy +builds: + # You can have multiple builds defined as a yaml list + - # ID of the build. + # Defaults to the project name. + id: "hkn" + + # Path to main.go file or main package. + # Default is `.`. + main: ./client/main.go + + # Binary name. + # Can be a path (e.g. `bin/app`) to wrap the binary in a directory. + # Default is the name of the project directory. + binary: hkn + + # Custom flags templates. + # Default is empty. + flags: + - -tags=netgo + + # Custom ldflags templates. + # Default is `-s -w -X main.version={{.Version}} -X main.commit={{.ShortCommit}} -X main.date={{.Date}} -X main.builtBy=goreleaser`. + ldflags: + - -s -w -X github.com/aau-network-security/haaukins/client/cli.Version={{ .Version }} + + # Custom environment variables to be set during the builds. + # Default is empty. + env: + - CGO_ENABLED=0 + + # GOOS list to build for. + # For more info refer to: https://golang.org/doc/install/source#environment + # Defaults are darwin and linux. + goos: + - windows + - darwin + - linux + + # GOARCH to build for. + # For more info refer to: https://golang.org/doc/install/source#environment + # Defaults are 386 and amd64. + goarch: + - amd64 + - 386 + + - id: "hknd" + env: + - CGO_ENABLED=0 + main: ./main.go + binary: hknd + flags: + - -tags=netgo + ldflags: + - -s -w -X github.com/aau-network-security/haaukins/daemon.Version={{ .Version }} + goos: + - linux + goarch: + - 386 + - amd64 + +archives: + - # ID of this archive. + # Defaults to `default`. + id: haaukins + + # Builds reference which build instances should be archived in this archive. + builds: + - hknd + + # Archive name template. + # Defaults: + # - if format is `tar.gz`, `gz` or `zip`: + # - `{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}` + # - if format is `binary`: + # - `{{ .Binary }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}` + name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}" + + + # Replacements for GOOS and GOARCH in the archive name. + # Keys should be valid GOOSs or GOARCHs. + # Values are the respective replacements. + # Default is empty. + replacements: + amd64: 64-bit + + + # Set to true, if you want all files in the archive to be in a single directory. + # If set to true and you extract the archive 'goreleaser_Linux_arm64.tar.gz', + # you get a folder 'goreleaser_Linux_arm64'. + # If set to false, all files are extracted separately. + # You can also set it to a custom folder name (templating is supported). + # Default is false. + wrap_in_directory: true + + # Archive format. Valid options are `tar.gz`, `gz`, `zip` and `binary`. + # If format is `binary`, no archives are created and the binaries are instead + # uploaded directly. + # Default is `tar.gz`. + format: zip + + # Additional files/globs you want to add to the archive. + # Defaults are any files matching `LICENCE*`, `LICENSE*`, + # `README*` and `CHANGELOG*` (case-insensitive). + files: + - LICENSE + - readme.md + - dist/CHANGELOG.md + + + - # ID of this archive. + id: haaukins_client + + # Builds reference which build instances should be archived in this archive. + builds: + - hkn + + # Archive name template. + # Defaults: + # - if format is `tar.gz`, `gz` or `zip`: + # - `{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}` + # - if format is `binary`: + # - `{{ .Binary }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}` + + name_template: "{{ .ProjectName }}_cli_{{ .Version }}_{{ .Os }}_{{ .Arch }}" + + + # Replacements for GOOS and GOARCH in the archive name. + # Keys should be valid GOOSs or GOARCHs. + # Values are the respective replacements. + # Default is empty. + replacements: + amd64: 64-bit + 386: 32-bit + darwin: macOS + + # Set to true, if you want all files in the archive to be in a single directory. + # If set to true and you extract the archive 'goreleaser_Linux_arm64.tar.gz', + # you get a folder 'goreleaser_Linux_arm64'. + # If set to false, all files are extracted separately. + # You can also set it to a custom folder name (templating is supported). + # Default is false. + wrap_in_directory: true + + # Archive format. Valid options are `tar.gz`, `gz`, `zip` and `binary`. + # If format is `binary`, no archives are created and the binaries are instead + # uploaded directly. + # Default is `tar.gz`. + format: zip + + # Additional files/globs you want to add to the archive. + # Defaults are any files matching `LICENCE*`, `LICENSE*`, + # `README*` and `CHANGELOG*` (case-insensitive). + files: + - LICENSE + - readme.md + - dist/CHANGELOG.md + +checksum: + # You can change the name of the checksums file. + # Default is `{{ .ProjectName }}_{{ .Version }}_checksums.txt`. + name_template: "v_{{ .Version }}_{{ .ProjectName }}_checksums.txt" + + # Algorithm to be used. + # Accepted options are sha256, sha512, sha1, crc32, md5, sha224 and sha384. + # Default is sha256. + algorithm: sha256 + +release: + prerelease: true +# it will be enabled later +# brews: +# - +# ids: +# - haaukins_client +# tap: +# owner: aau-network-security +# name: homebrew-haaukins +# folder: Formula +# homepage: https://github.com/aau-network-security/haaukins/ +# description: Haaukins CLI +# install: | +# bin.install "hkn" +# test: | +# system "#{bin}/hkn --help" diff --git a/daemon/daemon.go b/daemon/daemon.go index 52ad10d2..89d8017d 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -327,24 +327,26 @@ func New(conf *Config) (*daemon, error) { exClient: exServiceClient, } + var vpnAddress string for _, ef := range dbEvents { + var eventConfig store.EventConfig // check through status of event // suspended is also included since at first start // daemon should be aware of the event which is suspended // and configuration should be loaded to daemon if ef.Status == Running || ef.Status == Suspended { - vpnIP, err := getVPNIP() - if err != nil { - log.Error().Msgf("Getting VPN address error on New() in daemon %v", err) + if ef.OnlyVPN == int32(VPN) || ef.OnlyVPN == int32(VPNBrowser) { + vpnIP, err := getVPNIP() + if err != nil { + log.Error().Msgf("Getting VPN address error on New() in daemon %v", err) + } + vpnAddress = fmt.Sprintf("%s.240.1/22", vpnIP) } - vpnAddress := fmt.Sprintf("%s.240.1/22", vpnIP) - if ef.Status == Suspended { d.dbClient.SetEventStatus(ctx, &pbc.SetEventStatusRequest{Status: Running, EventTag: ef.Tag}) ef.Status = Running } - eventConfig = d.generateEventConfig(ef, ef.Status, vpnAddress) err = d.createEventFromEventDB(context.Background(), eventConfig) if err != nil { diff --git a/daemon/event.go b/daemon/event.go index bca3b7da..f72b3b7b 100644 --- a/daemon/event.go +++ b/daemon/event.go @@ -67,6 +67,7 @@ func (d *daemon) startEvent(ev guacamole.Event) { func (d *daemon) CreateEvent(req *pb.CreateEventRequest, resp pb.Daemon_CreateEventServer) error { loggerInstance := &GrpcLogger{resp: resp} + vpnAddress := "" ctx := context.WithValue(resp.Context(), "grpc_logger", loggerInstance) log.Ctx(ctx). Info(). @@ -161,7 +162,7 @@ func (d *daemon) CreateEvent(req *pb.CreateEventRequest, resp pb.Daemon_CreateEv resp.Send(&pb.LabStatus{ErrorMessage: NotAvailableTag}) return fmt.Errorf(NotAvailableTag) } - + log.Debug().Msgf("Checked existing events through database.") // difference in days // if there is no difference in days it means event will be // started immediately @@ -174,13 +175,16 @@ func (d *daemon) CreateEvent(req *pb.CreateEventRequest, resp pb.Daemon_CreateEv return nil } // 25.43 - vpnIp, err := getVPNIP() - if err != nil { - log.Error().Msgf("Get VPN IP error %v, from CreateEvent function", err) + if req.OnlyVPN == int32(VPN) || req.OnlyVPN == int32(VPNBrowser) { + vpnIP, err := getVPNIP() + if err != nil { + log.Error().Msgf("Error on getting IP for VPN connection error: %v", err) + } + vpnAddress = fmt.Sprintf("%s.240.1/22", vpnIP) } secretKey := strings.TrimSpace(req.SecretEvent) - vpnAddress := fmt.Sprintf("%s.240.1/22", vpnIp) + conf := store.EventConfig{ Name: req.Name, Tag: evtag, @@ -204,7 +208,8 @@ func (d *daemon) CreateEvent(req *pb.CreateEventRequest, resp pb.Daemon_CreateEv if err := conf.Validate(); err != nil { return err } - + log.Debug().Str("event", string(conf.Tag)). + Msgf("Event configuration is validated") _, err = d.eventPool.GetEvent(evtag) if err == nil { @@ -361,7 +366,13 @@ func (d *daemon) StopEvent(req *pb.StopEventRequest, resp pb.Daemon_StopEventSer if err != nil { return err } - createdBy := ev.GetConfig().CreatedBy + eventConfig := ev.GetConfig() + createdBy := eventConfig.CreatedBy + if eventConfig.OnlyVPN == VPN || eventConfig.OnlyVPN == VPNBrowser { + vpnIP := strings.ReplaceAll(eventConfig.VPNAddress, ".240.1/22", "") + vpnIPPools.ReleaseIP(vpnIP) + log.Debug().Str("VPN IP", vpnIP).Msgf("VPN IP is released from allocated pool!") + } if (user.NPUser && user.Username == createdBy) || !user.NPUser { _, err := d.dbClient.SetEventStatus(ctx, &pbc.SetEventStatusRequest{EventTag: string(evtag), Status: Closed}) @@ -570,6 +581,7 @@ func removeDuplicates(exercises []string) []string { func (d *daemon) visitBookedEvents() error { ctx := context.Background() now := time.Now() + vpnAddress := "" eventResponse, err := d.dbClient.GetEvents(context.Background(), &pbc.GetEventRequest{Status: Booked}) if err != nil { log.Warn().Msgf("checking booked events error %v", err) @@ -579,11 +591,14 @@ func (d *daemon) visitBookedEvents() error { requestedStartTime, _ := time.Parse(displayTimeFormat, event.StartedAt) if requestedStartTime.Before(now) || requestedStartTime.Equal(now) { // set status to running if booked event startTime passed. - vpnIP, err := getVPNIP() - if err != nil { - log.Error().Msgf("Error on getting IP for VPN connection error: %v", err) + + if event.OnlyVPN == int32(VPN) || event.OnlyVPN == int32(VPNBrowser) { + vpnIP, err := getVPNIP() + if err != nil { + log.Error().Msgf("Error on getting IP for VPN connection error: %v", err) + } + vpnAddress = fmt.Sprintf("%s.240.1/22", vpnIP) } - vpnAddress := fmt.Sprintf("%s.240.1/22", vpnIP) eventConfig := d.generateEventConfig(event, Running, vpnAddress) if err := d.createEventFromEventDB(ctx, eventConfig); err != nil { @@ -648,7 +663,7 @@ func (d *daemon) generateEventConfig(event *pbc.GetEventResponse_Events, status Status: status, CreatedBy: event.CreatedBy, VPNAddress: vpnAddress, - OnlyVPN: event.OnlyVPN, + OnlyVPN: event.OnlyVPN, // 0 NoVPN 1 VPN 2 Browser+VPN SecretKey: event.SecretKey, } @@ -677,25 +692,29 @@ func (d *daemon) closeEvent(ch chan guacamole.Event, wg *sync.WaitGroup) error { closer := func(ev guacamole.Event) { e := ev.GetConfig() log.Info().Msgf("Running close events, checking %s", e.Tag) + if e.OnlyVPN == VPN || e.OnlyVPN == VPNBrowser { + vpnIP := strings.ReplaceAll(e.VPNAddress, ".240.1/22", "") + vpnIPPools.ReleaseIP(vpnIP) + log.Debug().Str("VPN IP", vpnIP).Msgf("VPNIP is released from allocated pool!") + } - eTag := store.Tag(e.Tag) if isDelayed(e.FinishExpected.Format(displayTimeFormat)) { currentTime := strconv.Itoa(int(time.Now().Unix())) newEventTag := fmt.Sprintf("%s-%s", e.Tag, currentTime) - event, err := d.eventPool.GetEvent(eTag) + event, err := d.eventPool.GetEvent(e.Tag) if err != nil { log.Warn().Msgf("event pool get event error %v ", err) closeErr = err } _, err = d.dbClient.SetEventStatus(ctx, &pbc.SetEventStatusRequest{ - EventTag: string(eTag), + EventTag: string(e.Tag), Status: Closed}) if err != nil { - log.Warn().Msgf("Error in setting up status of event in database side event: %s", string(eTag)) + log.Warn().Msgf("Error in setting up status of event in database side event: %s", string(e.Tag)) closeErr = err } - log.Debug().Msgf("Status is set to %d for event: %s", Closed, string(eTag)) - if err := d.eventPool.RemoveEvent(eTag); err != nil { + log.Debug().Msgf("Status is set to %d for event: %s", Closed, string(e.Tag)) + if err := d.eventPool.RemoveEvent(e.Tag); err != nil { closeErr = err } if err := event.Close(); err != nil { diff --git a/daemon/exercise.go b/daemon/exercise.go index c717c672..b6e5551d 100644 --- a/daemon/exercise.go +++ b/daemon/exercise.go @@ -10,7 +10,6 @@ import ( pb "github.com/aau-network-security/haaukins/daemon/proto" eproto "github.com/aau-network-security/haaukins/exercise/ex-proto" - "github.com/aau-network-security/haaukins/lab" "github.com/aau-network-security/haaukins/store" storeProto "github.com/aau-network-security/haaukins/store/proto" "github.com/golang/protobuf/jsonpb" @@ -280,17 +279,20 @@ func (d *daemon) AddChallenge(req *pb.AddChallengeRequest, srv pb.Daemon_AddChal } } + ev.PauseSignup(true) var addChalError error - var lb interface{} - sendBackLab := make(chan lab.Lab) - go func() { - lb = <-ev.GetHub().Queue() - if err := lb.(lab.Lab).AddChallenge(ctx, exers...); err != nil { - addChalError = err - } - sendBackLab <- lb.(lab.Lab) - ev.GetHub().Update(sendBackLab) - }() + + for _, lb := range ev.GetHub().Labs() { + waitGroup.Add(1) + go func() { + if err := lb.AddChallenge(ctx, exers...); err != nil { + addChalError = err + } + waitGroup.Done() + }() + waitGroup.Wait() + } + ev.PauseSignup(false) frontendData := ev.GetFrontendData() for tid, l := range ev.GetAssignedLabs() { diff --git a/daemon/net.go b/daemon/net.go index c0bb37a0..d9aaae37 100644 --- a/daemon/net.go +++ b/daemon/net.go @@ -10,7 +10,7 @@ import ( ) var ( - NoAvailableIPsErr = errors.New("no available IPs") + NoAvailableIPsErr = errors.New("no available VPN IPs") ) type IPPool struct { @@ -48,11 +48,10 @@ func (ipp *IPPool) Get() (string, error) { genIP := func() string { ip := randomPickWeighted(ipp.weights) switch ip { - case "192": - ip += ".168" - case "172": - // valid up to 172.25 | 172.26 | 172.27 | 172.28 | 172.29 | 172.30 | 172.31 - ip += fmt.Sprintf(".%d", rand.Intn(6)+25) + case "25": + ip += fmt.Sprintf(".%d", rand.Intn(255)) + case "35": + ip += fmt.Sprintf(".%d", rand.Intn(255)) } return ip @@ -70,12 +69,17 @@ func (ipp *IPPool) Get() (string, error) { return ip, nil } +func (ipp *IPPool) ReleaseIP(ip string) { + ipp.m.Lock() + defer ipp.m.Unlock() + delete(ipp.ips, ip) +} + func newIPPoolFromHost() *IPPool { ips := map[string]struct{}{} weights := map[string]int{ - "172": 255 * 255, // 172.{2nd}.{0-255}.{0-255} => 2nd => 25-31 => 6 + 1 => 7 - - "192": 1 * 255, // 10.{2nd}.{0-255}.{0-255} => 2nd => 0-254 => 254 + 1 => 255 + "35": 1 * 255, // 172.{2nd}.{0-255}.{0-255} => 2nd => 25-31 => 6 + 1 => 7 + "25": 255 * 255, } ifaces, err := net.Interfaces() diff --git a/lab/hub.go b/lab/hub.go index 5a277c93..49d88d07 100644 --- a/lab/hub.go +++ b/lab/hub.go @@ -24,6 +24,7 @@ type Hub interface { Suspend(context.Context) error Resume(context.Context) error Update(labTag <-chan Lab) + Labs() map[string]Lab UpdateExercises(exercises []store.Exercise) } @@ -212,3 +213,7 @@ func (h *hub) Resume(ctx context.Context) error { return resumeError } + +func (h *hub) Labs() map[string]Lab { + return h.labs +} diff --git a/svcs/amigo/amigo.go b/svcs/amigo/amigo.go index 4da40ba0..bf362320 100644 --- a/svcs/amigo/amigo.go +++ b/svcs/amigo/amigo.go @@ -67,6 +67,7 @@ type siteInfo struct { Hosts []Hosts Notification Notification LabSubnet string + SignupPaused bool // this is used when add challenge is executed in the platform } type team struct { @@ -134,6 +135,10 @@ func (am *Amigo) SetNotification(n Notification) { am.globalInfo.Notification = n } +func (am *Amigo) PauseSignup(pause bool) { + am.globalInfo.SignupPaused = pause +} + func (am *Amigo) getSiteInfo(w http.ResponseWriter, r *http.Request) siteInfo { info := am.globalInfo diff --git a/svcs/amigo/resources/private/info.tmpl.html b/svcs/amigo/resources/private/info.tmpl.html index f6efe992..9e3aa4be 100644 --- a/svcs/amigo/resources/private/info.tmpl.html +++ b/svcs/amigo/resources/private/info.tmpl.html @@ -46,8 +46,8 @@

Installation instructions for all clients: https://www.wireguard.com/install/

User Scripts

-
  • Install Wireguard script : install_wireguard.sh
  • -
  • Connect Event script: connect_event.py
  • +
  • Install Wireguard script : install_wireguard.sh
  • +
  • Connect Event script: connect_event.py
  • Connect Event script is making your Wireguard interface up and modifies your /etc/hosts file
  • If you notice outdated information, help us to update :)
  • Use the scripts with your responsibility.
  • @@ -149,4 +149,4 @@

    -{{ end }} \ No newline at end of file +{{ end }} diff --git a/svcs/amigo/resources/private/signup.tmpl.html b/svcs/amigo/resources/private/signup.tmpl.html index f53c47c1..f74b27f8 100644 --- a/svcs/amigo/resources/private/signup.tmpl.html +++ b/svcs/amigo/resources/private/signup.tmpl.html @@ -4,56 +4,63 @@