diff --git a/Makefile b/Makefile index 278134f..1359610 100644 --- a/Makefile +++ b/Makefile @@ -50,6 +50,10 @@ pkg/wg-manager: pkg/wg-manager/cmd/register.go pkg/wg-manager/cmd/main.go pkg/wg server.iso: server.yml students.csv ssl/fullchain.pem ssl/privkey.pem challenge-initrd.img pkg/arp-spoofer pkg/login-validator pkg/monit pkg/postfix pkg/tftpd pkg/unbound pkg/wg-manager challenge-kernel login-initrd.img linuxkit build -docker -format iso-bios $< +pkg/debian-tuto2: pkg/debian-tuto2/sshd_config pkg/debian-tuto2/gai.conf pkg/debian-tuto2/isolinux.cfg pkg/debian-tuto2/build.yml pkg/debian-tuto2/default.script pkg/debian-tuto2/issue pkg/debian-tuto2/Dockerfile + linuxkit pkg build -org nemunaire pkg/debian-tuto2/ + touch pkg/debian-tuto2 + tuto2-kernel: tuto2.yml linuxkit build -docker $< tuto2-initrd.img: tuto2.yml @@ -57,7 +61,7 @@ tuto2-initrd.img: tuto2.yml tuto2-cmdline: tuto2.yml linuxkit build -docker $< -tuto2.iso: tuto2.yml tuto2-kernel tuto2-initrd.img tuto2-cmdline +tuto2.iso: tuto2.yml pkg/debian-tuto2 tuto2-kernel tuto2-initrd.img tuto2-cmdline linuxkit build -docker -format iso-bios $< tuto2-srs.iso: tuto2.iso pkg/debian-tuto2/isolinux.cfg diff --git a/checker/checker.go b/checker/checker.go index ddddaad..5d510aa 100644 --- a/checker/checker.go +++ b/checker/checker.go @@ -1,12 +1,14 @@ package main import ( + "encoding/json" "errors" "fmt" "io/ioutil" "log" "net" "net/http" + "strings" "time" "github.com/miekg/dns" @@ -15,6 +17,10 @@ import ( "git.nemunai.re/lectures/adlin/libadlin" ) +const ( + DEFAULT_RESOLVER = "2a01:e0a:2b:2250::1" +) + var verbose = false // ICMP @@ -25,6 +31,7 @@ func check_ping(ip string, cb func(pkt *ping.Packet)) (err error) { if err != nil { return } + defer pinger.Stop() pinger.Timeout = time.Second * 5 pinger.Count = 1 @@ -46,7 +53,7 @@ func get_GLUE(domain string) (aaaa net.IP, err error) { m.SetEdns0(4096, true) var r *dns.Msg - r, _, err = client.Exchange(m, "[2a01:e0a:25a:9160::2]:53") + r, _, err = client.Exchange(m, "[2a01:e0a:2b:2250::b]:53") if err != nil { return } @@ -86,8 +93,10 @@ func check_dns(domain, ip string) (aaaa net.IP, err error) { err = errors.New("failed to get a valid answer") } - if len(r.Answer) > 0 { - aaaa = r.Answer[0].(*dns.AAAA).AAAA + for _, answer := range r.Answer { + if t, ok := answer.(*dns.AAAA); ok { + aaaa = t.AAAA + } } return @@ -95,19 +104,33 @@ func check_dns(domain, ip string) (aaaa net.IP, err error) { // PORT 80 -func check_http(ip string) (err error) { +func check_http(ip, dn string) (err error) { client := &http.Client{ CheckRedirect: func(req *http.Request, via []*http.Request) error { return http.ErrUseLastResponse }, } + req, errr := http.NewRequest("GET", fmt.Sprintf("http://[%s]/", ip), nil) + if errr != nil { + return errr + } + + if dn != "" { + req.Header.Add("Host", strings.TrimSuffix(dn, ".")) + } + var resp *http.Response - resp, err = client.Get(fmt.Sprintf("http://[%s]/", ip)) + resp, err = client.Do(req) if err != nil { return } defer resp.Body.Close() + + if dn != "" && resp.StatusCode >= 400 { + return fmt.Errorf("Bad status, got: %d (%s)", resp.StatusCode, resp.Status) + } + _, err = ioutil.ReadAll(resp.Body) return } @@ -116,18 +139,97 @@ func check_http(ip string) (err error) { func check_https(domain, ip string) (err error) { var resp *http.Response - resp, err = http.Get(fmt.Sprintf("https://%s/", domain)) + resp, err = http.Get(fmt.Sprintf("https://%s/", strings.TrimSuffix(domain, "."))) if err != nil { return } defer resp.Body.Close() + + if resp.StatusCode >= 300 && resp.StatusCode < 400 { + loc := resp.Header.Get("Location") + if loc != "" && strings.HasSuffix(dns.Fqdn(loc), domain) { + if dns.Fqdn(loc) == domain { + return fmt.Errorf("Redirection loop %s redirect to %s", domain, loc) + } else if err = check_https(dns.Fqdn(loc), ip); err != nil { + return fmt.Errorf("Error after following redirection to %s: %w", loc, err) + } else { + return + } + } + } + + if resp.StatusCode >= 300 { + return fmt.Errorf("Bad status, got: %d (%s)", resp.StatusCode, resp.Status) + } + _, err = ioutil.ReadAll(resp.Body) return } +// MATRIX + +type matrix_result struct { + WellKnownResult struct { + Result string `json:"result"` + } + DNSResult struct { + SRVError *struct { + Message string + } + } + ConnectionReports map[string]struct { + Errors []string + } + ConnectionErrors map[string]struct { + Message string + } + Version struct { + Name string `json:"name"` + Version string `json:"version"` + } + FederationOK bool +} + +func check_matrix(domain string) (version string, err error) { + var resp *http.Response + resp, err = http.Get(fmt.Sprintf("https://federation-tester.adlin.nemunai.re/api/report?server_name=%s", strings.TrimSuffix(domain, "."))) + if err != nil { + return + } + defer resp.Body.Close() + + if resp.StatusCode >= 300 { + return "", fmt.Errorf("Sorry, the federation tester is broken. Check on https://federationtester.matrix.org/#%s", domain) + } + + var federationTest matrix_result + if federationTest.FederationOK { + version = federationTest.Version.Name + " " + federationTest.Version.Version + return version, nil + } else if err = json.NewDecoder(resp.Body).Decode(&federationTest); err != nil { + log.Printf("Error in chech_matrix, when decoding json:", err.Error()) + return "", fmt.Errorf("Sorry, the federation tester is broken. Check on https://federationtester.matrix.org/#%s", domain) + } else if federationTest.DNSResult.SRVError != nil && federationTest.WellKnownResult.Result != "" { + return "", fmt.Errorf("%s OR %s", federationTest.DNSResult.SRVError.Message, federationTest.WellKnownResult.Result) + } else if len(federationTest.ConnectionErrors) > 0 { + var msg strings.Builder + for srv, cerr := range federationTest.ConnectionErrors { + if msg.Len() > 0 { + msg.WriteString("; ") + } + msg.WriteString(srv) + msg.WriteString(": ") + msg.WriteString(cerr.Message) + } + return "", fmt.Errorf("Connection errors: %s", msg.String()) + } else { + return "", fmt.Errorf("An unimplemented error occurs. Please report to nemunaire. But know that federation seems to be broken. Check https://federationtester.matrix.org/#%s", domain) + } +} + // Main -func minTunnelVersion(std adlin.Student) (int, error) { +func minTunnelVersion(std *adlin.Student, suffixip int) (int, error) { tunnels, err := std.GetTunnelTokens() if err != nil { return 0, err @@ -139,7 +241,7 @@ func minTunnelVersion(std adlin.Student) (int, error) { continue } - if tunnel.Dump != nil && tunnel.Version < minversion { + if tunnel.Dump != nil && tunnel.Version < minversion && suffixip == tunnel.SuffixIP { minversion = tunnel.Version } } @@ -153,110 +255,175 @@ func studentsChecker() { log.Println("Unable to check students:", err) return } - log.Println("Checking students...") + check_matrix_for := (time.Now().Second()/30)*5 + time.Now().Minute()%5 - for _, s := range students { + log.Printf("Checking students... (std_matrix%%10=%d)\n", check_matrix_for) + + for istd, s := range students { time.Sleep(250 * time.Millisecond) // Check ping std := s - stdIP := adlin.StudentIP(std.Id).String() + "1" - go check_ping(stdIP, func(pkt *ping.Packet) { - tunnel_version, err := minTunnelVersion(std) - if verbose { - log.Printf("%s PONG; version=%d (%v)\n", std.Login, tunnel_version, err) - } - std.OnPong(true) - - if tunnel_version == 2147483647 || tunnel_version == 0 { - log.Printf("%s unknown tunnel version: %d skipping tests (%v)", std.Login, tunnel_version, err) - return - } - - dnsIP := stdIP - // Is GLUE defined? - if glueIP, err := get_GLUE(std.MyDelegatedDomain()); glueIP != nil { - dnsIP = glueIP.String() + tuns, err := std.GetActivesTunnels() + if err != nil { + continue + } + for _, tun := range tuns { + stdIP := tun.GetStudentIP() + go check_ping(stdIP, func(pkt *ping.Packet) { + tunnel_version, err := minTunnelVersion(std, tun.SuffixIP) if verbose { - log.Printf("%s has defined GLUE: %s\n", std.Login, dnsIP) + log.Printf("%s PONG (on %x); version=%d (%v)\n", std.Login, tun.SuffixIP, tunnel_version, err) } - } else if err != nil { - log.Printf("%s and GLUE: %s\n", std.Login, err) - } + std.OnPong(true) - // Check DNS - if addr, err := check_dns(std.MyDelegatedDomain(), dnsIP); err == nil { - if verbose { - log.Printf("%s just unlocked DNS challenge\n", std.Login) + if tunnel_version == 2147483647 || tunnel_version == 0 { + log.Printf("%s unknown tunnel version: %d skipping tests (%v)", std.Login, tunnel_version, err) + return } - if _, err := std.UnlockNewChallenge(100*(tunnel_version-1)+2, ""); err != nil { - if _, err := std.UpdateUnlockedChallenge(100*(tunnel_version-1)+2, ""); err != nil { + + dnsIP := stdIP + var glueErr error + // Is GLUE defined? + if glueIP, err := get_GLUE(std.MyDelegatedDomain()); glueIP != nil { + dnsIP = glueIP.String() + + if verbose { + log.Printf("%s has defined GLUE: %s\n", std.Login, dnsIP) + } + } else if err != nil { + log.Printf("%s and GLUE: %s\n", std.Login, err) + glueErr = err + } + + // Check DNS + if addr, err := check_dns(std.MyDelegatedDomain(), dnsIP); err == nil { + if addr == nil { + dnsAt := " at " + dnsIP + if glueErr != nil { + dnsAt = " + there is a problem with the GLUE record: " + glueErr.Error() + } + if errreg := std.RegisterChallengeError(100*(tunnel_version-1)+3, fmt.Errorf("%s: empty response from the server%s", std.MyDelegatedDomain(), dnsAt)); errreg != nil { + log.Printf("Unable to register challenge error for %s: %s\n", std.Login, errreg) + } + } else { + if verbose { + log.Printf("%s just unlocked DNS challenge\n", std.Login) + } + if _, err := std.UnlockChallenge(100*(tunnel_version-1)+3, addr.String()); err != nil { + log.Printf("Unable to register challenge for %s: %s\n", std.Login, err.Error()) + } + + // Check HTTP with DNS + if glueErr != nil { + std.RegisterChallengeError(100*(tunnel_version-1)+4, fmt.Errorf("Unable to perform the test due to GLUE problem: %w", err)) + } else if err := check_http(addr.String(), std.MyDelegatedDomain()); err == nil { + if verbose { + log.Printf("%s just unlocked HTTP challenge\n", std.Login) + } + if _, err := std.UnlockChallenge(100*(tunnel_version-1)+4, ""); err != nil { + log.Printf("Unable to register challenge for %s: %s\n", std.Login, err.Error()) + } + } else { + std.RegisterChallengeError(100*(tunnel_version-1)+4, err) + if verbose { + log.Printf("%s and HTTP (with DNS ip=%s): %s\n", std.Login, addr.String(), err) + } + } + + // Check HTTPs with DNS + if glueErr != nil { + std.RegisterChallengeError(100*(tunnel_version-1)+5, fmt.Errorf("Unable to perform the test due to GLUE problem: %w", err)) + } else if err := check_https(std.MyDelegatedDomain(), addr.String()); err == nil { + if verbose { + log.Printf("%s just unlocked HTTPS challenge\n", std.Login) + } + if _, err := std.UnlockChallenge(100*(tunnel_version-1)+5, ""); err != nil { + log.Printf("Unable to register challenge for %s: %s\n", std.Login, err.Error()) + } + } else { + std.RegisterChallengeError(100*(tunnel_version-1)+5, err) + if verbose { + log.Printf("%s and HTTPS (with DNS ip=%s): %s\n", std.Login, addr.String(), err) + } + } + + // Check Matrix (only if GLUE Ok and) + if glueErr == nil && istd%10 == check_matrix_for { + if v, err := check_matrix(std.MyDelegatedDomain()); err == nil { + if verbose { + log.Printf("%s just unlocked Matrix challenge\n", std.Login) + } + if _, err := std.UnlockChallenge(100*(tunnel_version-1)+6, v); err != nil { + log.Printf("Unable to register challenge for %s: %s\n", std.Login, err.Error()) + } + } else { + std.RegisterChallengeError(100*(tunnel_version-1)+6, err) + if verbose { + log.Printf("%s and Matrix: %s\n", std.Login, err) + } + } + } + } + } else { + if errreg := std.RegisterChallengeError(100*(tunnel_version-1)+3, err); errreg != nil { + log.Printf("Unable to register challenge error for %s: %s\n", std.Login, errreg) + } + if verbose { + log.Printf("%s and DNS: %s\n", std.Login, err) + } + } + + // Check HTTP without DNS + if err := check_http(stdIP, ""); err == nil { + if verbose { + log.Printf("%s just unlocked HTTP IP (without DNS) challenge\n", std.Login) + } + if _, err := std.UnlockChallenge(100*(tunnel_version-1)+0, ""); err != nil { log.Printf("Unable to register challenge for %s: %s\n", std.Login, err.Error()) } + } else { + std.RegisterChallengeError(100*(tunnel_version-1)+0, err) + if verbose { + log.Printf("%s and HTTP IP (without DNS): %s\n", std.Login, err) + } } - // Check HTTP with DNS - if addr == nil { - log.Printf("%s and HTTP (with DNS ip=%s): skipped due to empty response\n", std.Login, addr.String()) - } else if err := check_http(addr.String()); err == nil { - if verbose { - log.Printf("%s just unlocked HTTP challenge\n", std.Login) - } - if _, err := std.UnlockNewChallenge(100*(tunnel_version-1)+0, ""); err != nil { - if _, err := std.UpdateUnlockedChallenge(100*(tunnel_version-1)+0, ""); err != nil { + // Check DNS for association + if addr, err := check_dns(std.MyAssociatedDomain(), DEFAULT_RESOLVER); err == nil { + // Check HTTP on delegated domain + if err := check_http(addr.String(), std.MyAssociatedDomain()); err == nil { + if verbose { + log.Printf("%s just unlocked HTTP (without DNS) challenge\n", std.Login) + } + if _, err := std.UnlockChallenge(100*(tunnel_version-1)+1, ""); err != nil { log.Printf("Unable to register challenge for %s: %s\n", std.Login, err.Error()) } + } else { + std.RegisterChallengeError(100*(tunnel_version-1)+1, err) + if verbose { + log.Printf("%s and HTTP (without DNS): %s\n", std.Login, err) + } + } + + // Check HTTPs without DNS + if err := check_https(std.MyAssociatedDomain(), stdIP); err == nil { + if verbose { + log.Printf("%s just unlocked HTTPS challenge\n", std.Login) + } + if _, err := std.UnlockChallenge(100*(tunnel_version-1)+2, ""); err != nil { + log.Printf("Unable to register challenge for %s: %s\n", std.Login, err.Error()) + } + } else { + std.RegisterChallengeError(100*(tunnel_version-1)+2, err) + if verbose { + log.Printf("%s and HTTPS (without DNS): %s\n", std.Login, err) + } } - } else if verbose { - log.Printf("%s and HTTP (with DNS ip=%s): %s\n", std.Login, addr.String(), err) } - // Check HTTPs with DNS - if addr == nil { - log.Printf("%s and HTTPS (with DNS ip=%s): skipped due to empty response\n", std.Login, addr.String()) - } else if err := check_https(std.MyDelegatedDomain(), addr.String()); err == nil { - if verbose { - log.Printf("%s just unlocked HTTPS challenge\n", std.Login) - } - if _, err := std.UnlockNewChallenge(100*(tunnel_version-1)+1, ""); err != nil { - if _, err := std.UpdateUnlockedChallenge(100*(tunnel_version-1)+1, ""); err != nil { - log.Printf("Unable to register challenge for %s: %s\n", std.Login, err.Error()) - } - } - } else if verbose { - log.Printf("%s and HTTPS (with DNS ip=%s): %s\n", std.Login, addr.String(), err) - } - } else { - // Check HTTP without DNS - if err := check_http(stdIP); err == nil { - if verbose { - log.Printf("%s just unlocked HTTP challenge\n", std.Login) - } - if _, err := std.UnlockNewChallenge(100*(tunnel_version-1)+0, ""); err != nil { - if _, err := std.UpdateUnlockedChallenge(100*(tunnel_version-1)+0, ""); err != nil { - log.Printf("Unable to register challenge for %s: %s\n", std.Login, err.Error()) - } - } - } else if verbose { - log.Printf("%s and HTTP (without DNS): %s\n", std.Login, err) - } - - // Check HTTPs without DNS - if err := check_https(std.MyAssociatedDomain(), stdIP); err == nil { - if verbose { - log.Printf("%s just unlocked HTTPS challenge\n", std.Login) - } - if _, err := std.UnlockNewChallenge(100*(tunnel_version-1)+1, ""); err != nil { - if _, err := std.UpdateUnlockedChallenge(100*(tunnel_version-1)+1, ""); err != nil { - log.Printf("Unable to register challenge for %s: %s\n", std.Login, err.Error()) - } - } - } else if verbose { - log.Printf("%s and HTTPS (without DNS): %s\n", std.Login, err) - } - } - - return - }) + return + }) + } } } diff --git a/libadlin/db.go b/libadlin/db.go index 44551f1..5fafcc9 100644 --- a/libadlin/db.go +++ b/libadlin/db.go @@ -56,7 +56,9 @@ func DBCreate() (err error) { CREATE TABLE IF NOT EXISTS students( id_student INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT, login VARCHAR(255) NOT NULL UNIQUE, - time TIMESTAMP NOT NULL + time TIMESTAMP NOT NULL, + associatedDomain VARCHAR(255) UNIQUE, + delegatedDomain VARCHAR(255) UNIQUE ) DEFAULT CHARACTER SET = utf8 COLLATE = utf8_bin; `); err != nil { return @@ -93,6 +95,19 @@ CREATE TABLE IF NOT EXISTS student_challenges( CONSTRAINT token_found UNIQUE (id_student,challenge), FOREIGN KEY(id_student) REFERENCES students(id_student) ) DEFAULT CHARACTER SET = utf8 COLLATE = utf8_bin; +`); err != nil { + return err + } + if _, err := db.Exec(` +CREATE TABLE IF NOT EXISTS student_challenge_errors( + id_st INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT, + id_student INTEGER NOT NULL, + challenge INTEGER NOT NULL, + time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + error VARCHAR(255) NOT NULL, + CONSTRAINT token_found UNIQUE (id_student,challenge), + FOREIGN KEY(id_student) REFERENCES students(id_student) +) DEFAULT CHARACTER SET = utf8 COLLATE = utf8_bin; `); err != nil { return err } diff --git a/libadlin/domain.go b/libadlin/domain.go index 4a88dd2..4553012 100644 --- a/libadlin/domain.go +++ b/libadlin/domain.go @@ -6,22 +6,39 @@ import ( ) const ( - AssociatedDomainSuffix = "adlin2021.p0m.fr." + AssociatedDomainSuffix = "adlin2022.p0m.fr." DelegatedDomainSuffix = "srs.p0m.fr." ) -func (student Student) MyDelegatedDomain() string { - return fmt.Sprintf("%s.%s", strings.Trim(strings.Replace(student.Login, "_", "-", -1), "-_"), DelegatedDomainSuffix) +func (student *Student) MyDelegatedDomain() string { + if student.DelegatedDomain != nil { + return *student.DelegatedDomain + } else { + return fmt.Sprintf("%s.%s", strings.Trim(strings.Replace(student.Login, "_", "-", -1), "-_"), DelegatedDomainSuffix) + } } -func (student Student) MyAssociatedDomain() string { +func (student *Student) DefaultAssociatedDomain() string { return fmt.Sprintf("%s.%s", strings.Trim(strings.Replace(student.Login, "_", "-", -1), "-_"), AssociatedDomainSuffix) } -func (student Student) GetAssociatedDomains() (ds []string) { +func (student *Student) MyAssociatedDomain() string { + if student.AssociatedDomain != nil { + return *student.AssociatedDomain + } else { + return student.DefaultAssociatedDomain() + } +} + +func (student *Student) GetAssociatedDomains() (ds []string) { + defdn := student.DefaultAssociatedDomain() + ds = append(ds, defdn) + studentDomain := student.MyAssociatedDomain() - ds = append(ds, studentDomain) + if defdn != studentDomain { + ds = append(ds, studentDomain) + } return } diff --git a/libadlin/ping.go b/libadlin/ping.go index c8bef5a..a93fc9c 100644 --- a/libadlin/ping.go +++ b/libadlin/ping.go @@ -9,14 +9,14 @@ type Pong struct { State bool } -func (s Student) LastPongs() (pongs []Pong, err error) { +func (s *Student) LastPongs() (pongs []*Pong, err error) { if rows, errr := DBQuery("SELECT time, state FROM student_pong WHERE id_student = ? ORDER BY time DESC", s.Id); errr != nil { return nil, errr } else { defer rows.Close() for rows.Next() { - var p Pong + p := &Pong{} if err = rows.Scan(&p.Date, &p.State); err != nil { return } diff --git a/libadlin/session.go b/libadlin/session.go index 933f425..2665987 100644 --- a/libadlin/session.go +++ b/libadlin/session.go @@ -11,40 +11,41 @@ type Session struct { Time time.Time `json:"time"` } -func GetSession(id []byte) (s Session, err error) { +func GetSession(id []byte) (s *Session, err error) { + s = new(Session) err = DBQueryRow("SELECT id_session, id_student, time FROM student_sessions WHERE id_session=?", id).Scan(&s.Id, &s.IdStudent, &s.Time) return } -func NewSession() (Session, error) { +func NewSession() (*Session, error) { session_id := make([]byte, 255) if _, err := rand.Read(session_id); err != nil { - return Session{}, err + return nil, err } else if _, err := DBExec("INSERT INTO student_sessions (id_session, time) VALUES (?, ?)", session_id, time.Now()); err != nil { - return Session{}, err + return nil, err } else { - return Session{session_id, nil, time.Now()}, nil + return &Session{session_id, nil, time.Now()}, nil } } -func (student Student) NewSession() (Session, error) { +func (student *Student) NewSession() (*Session, error) { session_id := make([]byte, 255) if _, err := rand.Read(session_id); err != nil { - return Session{}, err + return nil, err } else if _, err := DBExec("INSERT INTO student_sessions (id_session, id_student, time) VALUES (?, ?, ?)", session_id, student.Id, time.Now()); err != nil { - return Session{}, err + return nil, err } else { - return Session{session_id, &student.Id, time.Now()}, nil + return &Session{session_id, &student.Id, time.Now()}, nil } } -func (s Session) SetStudent(student Student) (Session, error) { +func (s *Session) SetStudent(student *Student) (*Session, error) { s.IdStudent = &student.Id _, err := s.Update() return s, err } -func (s Session) Update() (int64, error) { +func (s *Session) Update() (int64, error) { if res, err := DBExec("UPDATE student_sessions SET id_student = ?, time = ? WHERE id_session = ?", s.IdStudent, s.Time, s.Id); err != nil { return 0, err } else if nb, err := res.RowsAffected(); err != nil { @@ -54,7 +55,7 @@ func (s Session) Update() (int64, error) { } } -func (s Session) Delete() (int64, error) { +func (s *Session) Delete() (int64, error) { if res, err := DBExec("DELETE FROM student_sessions WHERE id_session = ?", s.Id); err != nil { return 0, err } else if nb, err := res.RowsAffected(); err != nil { diff --git a/libadlin/ssh.go b/libadlin/ssh.go index 9e8f1be..f699860 100644 --- a/libadlin/ssh.go +++ b/libadlin/ssh.go @@ -16,14 +16,14 @@ type StudentKey struct { Time time.Time `json:"time"` } -func GetStudentKeys() (keys []StudentKey, err error) { +func GetStudentKeys() (keys []*StudentKey, err error) { if rows, errr := DBQuery("SELECT id_key, id_student, sshkey, time FROM student_keys"); errr != nil { return nil, errr } else { defer rows.Close() for rows.Next() { - var k StudentKey + k := &StudentKey{} if err = rows.Scan(&k.Id, &k.IdStudent, &k.Key, &k.Time); err != nil { return } @@ -37,14 +37,14 @@ func GetStudentKeys() (keys []StudentKey, err error) { } } -func (s Student) GetKeys() (keys []StudentKey, err error) { +func (s *Student) GetKeys() (keys []*StudentKey, err error) { if rows, errr := DBQuery("SELECT id_key, id_student, sshkey, time FROM student_keys WHERE id_student = ?", s.Id); errr != nil { return nil, errr } else { defer rows.Close() for rows.Next() { - var k StudentKey + k := &StudentKey{} if err = rows.Scan(&k.Id, &k.IdStudent, &k.Key, &k.Time); err != nil { return } @@ -58,12 +58,13 @@ func (s Student) GetKeys() (keys []StudentKey, err error) { } } -func getStudentKey(id int) (k StudentKey, err error) { +func getStudentKey(id int) (k *StudentKey, err error) { + k = new(StudentKey) err = DBQueryRow("SELECT id_key, id_student, sshkey, time FROM student_keys WHERE id_key=?", id).Scan(&k.Id, &k.IdStudent, &k.Key, &k.Time) return } -func (s Student) NewKey(key string) (k StudentKey, err error) { +func (s *Student) NewKey(key string) (k *StudentKey, err error) { // Check key before importing it cmd := exec.Command("ssh-keygen", "-l", "-f", "-") cmd.Stdin = strings.NewReader(key) @@ -101,20 +102,20 @@ func (s Student) NewKey(key string) (k StudentKey, err error) { key = keyf[0] + " " + keyf[1] if res, err := DBExec("INSERT INTO student_keys (id_student, sshkey, time) VALUES (?, ?, ?)", s.Id, key, time.Now()); err != nil { - return StudentKey{}, err + return nil, err } else if kid, err := res.LastInsertId(); err != nil { - return StudentKey{}, err + return nil, err } else { s.UnlockNewChallenge(11, "") - return StudentKey{kid, s.Id, key, time.Now()}, nil + return &StudentKey{kid, s.Id, key, time.Now()}, nil } } -func (k StudentKey) GetStudent() (Student, error) { +func (k *StudentKey) GetStudent() (*Student, error) { return GetStudent(int(k.IdStudent)) } -func (k StudentKey) Update() (int64, error) { +func (k *StudentKey) Update() (int64, error) { if res, err := DBExec("UPDATE student_keys SET id_student = ?, sshkey = ?, time = ? WHERE id_key = ?", k.IdStudent, k.Key, k.Time, k.Id); err != nil { return 0, err } else if nb, err := res.RowsAffected(); err != nil { @@ -124,7 +125,7 @@ func (k StudentKey) Update() (int64, error) { } } -func (k StudentKey) Delete() (int64, error) { +func (k *StudentKey) Delete() (int64, error) { if res, err := DBExec("DELETE FROM student_keys WHERE id_key = ?", k.Id); err != nil { return 0, err } else if nb, err := res.RowsAffected(); err != nil { diff --git a/libadlin/students.go b/libadlin/students.go index c4790da..9903cc0 100644 --- a/libadlin/students.go +++ b/libadlin/students.go @@ -3,26 +3,29 @@ package adlin import ( "crypto/hmac" "crypto/sha512" + "fmt" "time" ) type Student struct { - Id int64 `json:"id"` - Login string `json:"login"` - Time *time.Time `json:"time"` - IP *string `json:"ip"` - MAC *string `json:"mac"` + Id int64 `json:"id"` + Login string `json:"login"` + Time *time.Time `json:"time"` + IP *string `json:"ip"` + MAC *string `json:"mac"` + AssociatedDomain *string `json:"associated_domain,omitempty"` + DelegatedDomain *string `json:"delegated_domain,omitempty"` } -func GetStudents() (students []Student, err error) { - if rows, errr := DBQuery("SELECT S.id_student, S.login, MAX(L.time), L.ip, L.mac FROM students S INNER JOIN (SELECT a.id_student, a.time, a.ip, a.mac FROM student_login a INNER JOIN (SELECT id_student, MAX(time) AS time FROM student_login GROUP BY id_student) b ON a.id_student = b.id_student AND a.time = b.time) L ON S.id_student = L.id_student GROUP BY id_student"); errr != nil { +func GetStudents() (students []*Student, err error) { + if rows, errr := DBQuery("SELECT S.id_student, S.login, MAX(L.time), L.ip, L.mac, S.associatedDomain, S.delegatedDomain FROM students S INNER JOIN (SELECT a.id_student, a.time, a.ip, a.mac FROM student_login a INNER JOIN (SELECT id_student, MAX(time) AS time FROM student_login GROUP BY id_student) b ON a.id_student = b.id_student AND a.time = b.time) L ON S.id_student = L.id_student GROUP BY id_student"); errr != nil { return nil, errr } else { defer rows.Close() for rows.Next() { - var s Student - if err = rows.Scan(&s.Id, &s.Login, &s.Time, &s.IP, &s.MAC); err != nil { + s := &Student{} + if err = rows.Scan(&s.Id, &s.Login, &s.Time, &s.IP, &s.MAC, &s.AssociatedDomain, &s.DelegatedDomain); err != nil { return } students = append(students, s) @@ -35,13 +38,15 @@ func GetStudents() (students []Student, err error) { } } -func GetStudent(id int) (s Student, err error) { - err = DBQueryRow("SELECT S.id_student, S.login, MAX(L.time), L.ip, L.mac FROM students S INNER JOIN (SELECT a.id_student, a.time, a.ip, a.mac FROM student_login a INNER JOIN (SELECT id_student, MAX(time) AS time FROM student_login GROUP BY id_student) b ON a.id_student = b.id_student AND a.time = b.time) L ON S.id_student = L.id_student WHERE S.id_student=?", id).Scan(&s.Id, &s.Login, &s.Time, &s.IP, &s.MAC) +func GetStudent(id int) (s *Student, err error) { + s = new(Student) + err = DBQueryRow("SELECT S.id_student, S.login, MAX(L.time), L.ip, L.mac, S.associatedDomain, S.delegatedDomain FROM students S INNER JOIN (SELECT a.id_student, a.time, a.ip, a.mac FROM student_login a INNER JOIN (SELECT id_student, MAX(time) AS time FROM student_login GROUP BY id_student) b ON a.id_student = b.id_student AND a.time = b.time) L ON S.id_student = L.id_student WHERE S.id_student=?", id).Scan(&s.Id, &s.Login, &s.Time, &s.IP, &s.MAC, &s.AssociatedDomain, &s.DelegatedDomain) return } -func GetStudentByLogin(login string) (s Student, err error) { - err = DBQueryRow("SELECT S.id_student, S.login, MAX(L.time), L.ip, L.mac FROM students S INNER JOIN (SELECT a.id_student, a.time, a.ip, a.mac FROM student_login a INNER JOIN (SELECT id_student, MAX(time) AS time FROM student_login GROUP BY id_student) b ON a.id_student = b.id_student AND a.time = b.time) L ON S.id_student = L.id_student WHERE login=?", login).Scan(&s.Id, &s.Login, &s.Time, &s.IP, &s.MAC) +func GetStudentByLogin(login string) (s *Student, err error) { + s = new(Student) + err = DBQueryRow("SELECT S.id_student, S.login, MAX(L.time), L.ip, L.mac, S.associatedDomain, S.delegatedDomain FROM students S INNER JOIN (SELECT a.id_student, a.time, a.ip, a.mac FROM student_login a INNER JOIN (SELECT id_student, MAX(time) AS time FROM student_login GROUP BY id_student) b ON a.id_student = b.id_student AND a.time = b.time) L ON S.id_student = L.id_student WHERE login=?", login).Scan(&s.Id, &s.Login, &s.Time, &s.IP, &s.MAC, &s.AssociatedDomain, &s.DelegatedDomain) return } @@ -51,23 +56,23 @@ func StudentExists(login string) bool { return err == nil && z == 1 } -func NewStudent(login string) (Student, error) { +func NewStudent(login string) (*Student, error) { t := time.Now() if res, err := DBExec("INSERT INTO students (login, time) VALUES (?, ?)", login, t); err != nil { - return Student{}, err + return nil, err } else if sid, err := res.LastInsertId(); err != nil { - return Student{}, err + return nil, err } else { - return Student{sid, login, &t, nil, nil}, nil + return &Student{sid, login, &t, nil, nil, nil, nil}, nil } } -func (s Student) GetPKey() []byte { +func (s *Student) GetPKey() []byte { return hmac.New(sha512.New512_224, []byte(SharedSecret)).Sum([]byte(s.Login)) } -func (s Student) Update() (int64, error) { - if res, err := DBExec("UPDATE students SET login = ?, time = ? WHERE id_student = ?", s.Login, s.Time, s.Id); err != nil { +func (s *Student) Update() (int64, error) { + if res, err := DBExec("UPDATE students SET login = ?, time = ?, associatedDomain = ?, delegatedDomain = ? WHERE id_student = ?", s.Login, s.Time, s.AssociatedDomain, s.DelegatedDomain, s.Id); err != nil { return 0, err } else if nb, err := res.RowsAffected(); err != nil { return 0, err @@ -76,7 +81,7 @@ func (s Student) Update() (int64, error) { } } -func (s Student) Delete() (int64, error) { +func (s *Student) Delete() (int64, error) { if res, err := DBExec("DELETE FROM students WHERE id_student = ?", s.Id); err != nil { return 0, err } else if nb, err := res.RowsAffected(); err != nil { @@ -100,18 +105,20 @@ type UnlockedChallenge struct { Id int64 `json:"id,omitempty"` IdStudent int64 `json:"id_student"` Challenge int `json:"challenge,omitempty"` - Time time.Time `json:"time"` + Time *time.Time `json:"time,omitempty"` Value interface{} `json:"value,omitempty"` + LastCheck *time.Time `json:"last_check,omitempty"` + Error string `json:"error,omitempty"` } -func (s Student) GetStates() (ucs []UnlockedChallenge, err error) { +func (s *Student) GetStates() (ucs []*UnlockedChallenge, err error) { if rows, errr := DBQuery("SELECT id_st, challenge, time FROM student_challenges WHERE id_student = ?", s.Id); errr != nil { return nil, errr } else { defer rows.Close() for rows.Next() { - var u UnlockedChallenge + u := &UnlockedChallenge{} u.IdStudent = s.Id if err = rows.Scan(&u.Id, &u.Challenge, &u.Time); err != nil { return @@ -126,14 +133,36 @@ func (s Student) GetStates() (ucs []UnlockedChallenge, err error) { } } -func (s Student) GetStatesByChallenge() (ucs []UnlockedChallenge, err error) { +func (s *Student) GetChallengeErrors() (ucs []*ErroredChallenge, err error) { + if rows, errr := DBQuery("SELECT id_st, challenge, time, error FROM student_challenge_errors WHERE id_student = ?", s.Id); errr != nil { + return nil, errr + } else { + defer rows.Close() + + for rows.Next() { + u := &ErroredChallenge{} + u.IdStudent = s.Id + if err = rows.Scan(&u.Id, &u.Challenge, &u.Time, &u.Error); err != nil { + return + } + ucs = append(ucs, u) + } + if err = rows.Err(); err != nil { + return + } + + return + } +} + +func (s *Student) GetStatesByChallenge() (ucs []*UnlockedChallenge, err error) { if rows, errr := DBQuery("SELECT id_st, challenge, MIN(time), value FROM student_challenges WHERE id_student = ? GROUP BY challenge, id_student", s.Id); errr != nil { return nil, errr } else { defer rows.Close() for rows.Next() { - var u UnlockedChallenge + u := &UnlockedChallenge{} u.IdStudent = s.Id if err = rows.Scan(&u.Id, &u.Challenge, &u.Time, &u.Value); err != nil { return @@ -148,25 +177,57 @@ func (s Student) GetStatesByChallenge() (ucs []UnlockedChallenge, err error) { } } -func (s Student) UnlockNewChallenge(challenge int, value string) (UnlockedChallenge, error) { +func (s *Student) UnlockChallenge(challenge int, value string) (uc *UnlockedChallenge, err error) { + if uc, err = s.UnlockNewChallenge(challenge, value); err != nil { + if uc, err = s.UpdateUnlockedChallenge(challenge, value); err != nil { + return + } + } + + s.RegisterChallengeError(challenge, fmt.Errorf("OK")) + + return +} + +func (s *Student) UnlockNewChallenge(challenge int, value string) (*UnlockedChallenge, error) { if res, err := DBExec("INSERT INTO student_challenges (id_student, challenge, time, value) VALUES (?, ?, ?, ?)", s.Id, challenge, time.Now(), value); err != nil { - return UnlockedChallenge{}, err + return nil, err } else if utid, err := res.LastInsertId(); err != nil { - return UnlockedChallenge{}, err + return nil, err } else { - return UnlockedChallenge{utid, s.Id, challenge, time.Now(), value}, err + now := time.Now() + return &UnlockedChallenge{utid, s.Id, challenge, &now, value, nil, ""}, err } } -func (s Student) UpdateUnlockedChallenge(challenge int, value string) (UnlockedChallenge, error) { +func (s *Student) UpdateUnlockedChallenge(challenge int, value string) (*UnlockedChallenge, error) { if _, err := DBExec("UPDATE student_challenges SET time = ?, value = ? WHERE id_student = ? AND challenge = ?", time.Now(), value, s.Id, challenge); err != nil { - return UnlockedChallenge{}, err + return nil, err } else { - return UnlockedChallenge{0, s.Id, challenge, time.Now(), value}, err + now := time.Now() + return &UnlockedChallenge{0, s.Id, challenge, &now, value, nil, ""}, err } } -func (s Student) RegisterAccess(ip, mac string) error { +type ErroredChallenge struct { + Id int64 `json:"id,omitempty"` + IdStudent int64 `json:"id_student"` + Challenge int `json:"challenge,omitempty"` + Time time.Time `json:"time"` + Error string `json:"error,omitempty"` +} + +func (s *Student) RegisterChallengeError(challenge int, err error) error { + if _, errr := DBExec("INSERT INTO student_challenge_errors (id_student, challenge, time, error) VALUES (?, ?, ?, ?)", s.Id, challenge, time.Now(), err.Error()); errr == nil { + return nil + } else if _, errr := DBExec("UPDATE student_challenge_errors SET time = ?, error = ? WHERE id_student = ? AND challenge = ?", time.Now(), err.Error(), s.Id, challenge); errr != nil { + return errr + } else { + return nil + } +} + +func (s *Student) RegisterAccess(ip, mac string) error { if res, err := DBExec("INSERT INTO student_login (id_student, ip, mac, time) VALUES (?, ?, ?, ?)", s.Id, ip, mac, time.Now()); err != nil { return err } else if _, err := res.LastInsertId(); err != nil { diff --git a/libadlin/tunnel.go b/libadlin/tunnel.go index 5a177b0..c22a520 100644 --- a/libadlin/tunnel.go +++ b/libadlin/tunnel.go @@ -12,10 +12,19 @@ import ( "time" ) +const StdNetmask = 80 + func StudentIP(idstd int64) net.IP { return net.ParseIP(fmt.Sprintf("2a01:e0a:2b:2252:%x::", idstd)) } +func StudentNet(idstd int64) *net.IPNet { + return &net.IPNet{ + IP: StudentIP(idstd), + Mask: net.CIDRMask(StdNetmask, 128), + } +} + type WGDump struct { PubKey string PSK string @@ -28,32 +37,32 @@ type WGDump struct { } var ( - wgDumpCache_data map[string]WGDump = nil + wgDumpCache_data map[string]*WGDump = nil wgDumpCache_time time.Time wgDumpCache_mutex sync.RWMutex ) -func _readWgDump() (wgd map[string]WGDump, err error) { +func _readWgDump() (wgd map[string]*WGDump, err error) { out, errr := exec.Command("wg", "show", "wg-adlin", "dump").Output() if errr != nil { return nil, errr } - wgd = map[string]WGDump{} + wgd = map[string]*WGDump{} for _, line := range strings.Split(string(out), "\n") { cols := strings.Fields(line) if len(cols) != 8 { continue } - wgd[cols[0]] = WGDump{cols[0], cols[1], cols[2], cols[3], cols[4], cols[5], cols[6], cols[7]} + wgd[cols[0]] = &WGDump{cols[0], cols[1], cols[2], cols[3], cols[4], cols[5], cols[6], cols[7]} } return } -func readWgDump() (wgd map[string]WGDump, err error) { +func readWgDump() (wgd map[string]*WGDump, err error) { wgDumpCache_mutex.RLock() defer wgDumpCache_mutex.RUnlock() @@ -94,38 +103,48 @@ type TunnelToken struct { Dump *WGDump } -func tokenFromText(token string) []byte { +func (tt *TunnelToken) GetStudentIP() string { + if tt.SuffixIP == 0 { + return fmt.Sprintf("%s%x", StudentIP(tt.IdStudent).String(), 1) + } else { + return fmt.Sprintf("%s%x", StudentIP(tt.IdStudent).String(), tt.SuffixIP) + } +} + +func TokenFromText(token string) []byte { sha := sha512.Sum512([]byte(token)) return sha[:] } -func GetTunnelToken(token []byte) (t TunnelToken, err error) { +func GetTunnelToken(token []byte) (t *TunnelToken, err error) { + t = new(TunnelToken) err = DBQueryRow("SELECT token, token_text, id_student, pubkey, time, suffixip, version FROM student_tunnel_tokens WHERE token=? ORDER BY time DESC", token).Scan(&t.token, &t.TokenText, &t.IdStudent, &t.PubKey, &t.Time, &t.SuffixIP, &t.Version) if err == nil && t.PubKey != nil { if wgd, errr := readWgDump(); errr == nil { if v, ok := wgd[base64.StdEncoding.EncodeToString(t.PubKey)]; ok { - t.Dump = &v + t.Dump = v } } } return } -func (student Student) NewTunnelToken(suffixip int) (t TunnelToken, err error) { +func (student *Student) NewTunnelToken(suffixip int) (t *TunnelToken, err error) { tok := make([]byte, 7) if _, err = rand.Read(tok); err != nil { return } - t.TokenText = strings.Replace(strings.Replace(strings.Replace(strings.Replace(strings.Replace(base64.RawStdEncoding.EncodeToString(tok), "/", ".", -1), "+", "_", -1), "O", "#", -1), "l", "$", -1), "I", ">", -1) - t.token = tokenFromText(t.TokenText) + t = new(TunnelToken) + t.TokenText = strings.Replace(strings.Replace(strings.Replace(strings.Replace(strings.Replace(base64.RawStdEncoding.EncodeToString(tok), "/", ".", -1), "+", "_", -1), "O", "<", -1), "l", "$", -1), "I", ">", -1) + t.token = TokenFromText(t.TokenText) t.IdStudent = student.Id _, err = DBExec("INSERT INTO student_tunnel_tokens (token, token_text, id_student, time, suffixip, version) VALUES (?, ?, ?, ?, ?, 0)", t.token, t.TokenText, student.Id, time.Now(), suffixip) return } -func (student Student) GetTunnelTokens() (ts []TunnelToken, err error) { +func (student *Student) GetTunnelTokens() (ts []*TunnelToken, err error) { if rows, errr := DBQuery("SELECT token, token_text, id_student, pubkey, time, suffixip, version FROM student_tunnel_tokens WHERE id_student = ? ORDER BY time DESC", student.Id); errr != nil { return nil, errr } else if wgd, errr := readWgDump(); errr != nil { @@ -134,13 +153,13 @@ func (student Student) GetTunnelTokens() (ts []TunnelToken, err error) { defer rows.Close() for rows.Next() { - var t TunnelToken + t := &TunnelToken{} if err = rows.Scan(&t.token, &t.TokenText, &t.IdStudent, &t.PubKey, &t.Time, &t.SuffixIP, &t.Version); err != nil { return } if t.PubKey != nil { if v, ok := wgd[base64.StdEncoding.EncodeToString(t.PubKey)]; ok { - t.Dump = &v + t.Dump = v } } ts = append(ts, t) @@ -153,7 +172,7 @@ func (student Student) GetTunnelTokens() (ts []TunnelToken, err error) { } } -func (student Student) GetActivesTunnels() (ts []TunnelToken, err error) { +func (student *Student) GetActivesTunnels() (ts []*TunnelToken, err error) { if rows, errr := DBQuery("SELECT token, token_text, id_student, pubkey, time, suffixip, version FROM student_tunnel_tokens WHERE id_student = ? ORDER BY time DESC", student.Id); errr != nil { return nil, errr } else if wgd, errr := readWgDump(); errr != nil { @@ -162,13 +181,13 @@ func (student Student) GetActivesTunnels() (ts []TunnelToken, err error) { defer rows.Close() for rows.Next() { - var t TunnelToken + t := &TunnelToken{} if err = rows.Scan(&t.token, &t.TokenText, &t.IdStudent, &t.PubKey, &t.Time, &t.SuffixIP, &t.Version); err != nil { return } if t.PubKey != nil { if v, ok := wgd[base64.StdEncoding.EncodeToString(t.PubKey)]; ok { - t.Dump = &v + t.Dump = v ts = append(ts, t) } } @@ -181,12 +200,13 @@ func (student Student) GetActivesTunnels() (ts []TunnelToken, err error) { } } -func (student Student) GetTunnelToken(token []byte) (t TunnelToken, err error) { +func (student *Student) GetTunnelToken(token []byte) (t *TunnelToken, err error) { + t = new(TunnelToken) err = DBQueryRow("SELECT token, token_text, id_student, pubkey, time, suffixip, version FROM student_tunnel_tokens WHERE token = ? AND id_student = ? ORDER BY time DESC", token, student.Id).Scan(&t.token, &t.TokenText, &t.IdStudent, &t.PubKey, &t.Time, &t.SuffixIP, &t.Version) if err == nil && t.PubKey != nil { if wgd, errr := readWgDump(); errr == nil { if v, ok := wgd[base64.StdEncoding.EncodeToString(t.PubKey)]; ok { - t.Dump = &v + t.Dump = v } } } @@ -194,7 +214,7 @@ func (student Student) GetTunnelToken(token []byte) (t TunnelToken, err error) { } func (t *TunnelToken) Update() (int64, error) { - newtoken := tokenFromText(t.TokenText) + newtoken := TokenFromText(t.TokenText) tm := time.Now() if res, err := DBExec("UPDATE student_tunnel_tokens SET token = ?, token_text = ?, id_student = ?, pubkey = ?, time = ?, suffixip = ?, version = ? WHERE token = ?", newtoken, t.TokenText, t.IdStudent, t.PubKey, tm, t.SuffixIP, t.Version, t.token); err != nil { @@ -208,14 +228,24 @@ func (t *TunnelToken) Update() (int64, error) { } } -func GetStudentsTunnels() (ts []TunnelToken, err error) { +func (t *TunnelToken) Delete() (int64, error) { + if res, err := DBExec("DELETE FROM student_tunnel_tokens WHERE token = ? AND id_student = ?", t.token, t.IdStudent); err != nil { + return 0, err + } else if nb, err := res.RowsAffected(); err != nil { + return 0, err + } else { + return nb, err + } +} + +func GetStudentsTunnels() (ts []*TunnelToken, err error) { if rows, errr := DBQuery("SELECT T.token, T.token_text, T.id_student, T.pubkey, T.time, T.suffixip, T.version FROM student_tunnel_tokens T INNER JOIN (SELECT B.id_student, MAX(B.time) AS time FROM student_tunnel_tokens B WHERE B.pubkey IS NOT NULL GROUP BY id_student) L ON T.id_student = L.id_student AND T.time = L.time"); errr != nil { return nil, errr } else { defer rows.Close() for rows.Next() { - var t TunnelToken + t := &TunnelToken{} if err = rows.Scan(&t.token, &t.TokenText, &t.IdStudent, &t.PubKey, &t.Time, &t.SuffixIP, &t.Version); err != nil { return } diff --git a/token-validator/auth.go b/token-validator/auth.go index f90fec9..97be90b 100644 --- a/token-validator/auth.go +++ b/token-validator/auth.go @@ -27,7 +27,7 @@ func init() { router.POST("/api/auth/logout", apiRawHandler(logout)) } -func validateAuthToken(s adlin.Student, _ httprouter.Params, _ []byte) (interface{}, error) { +func validateAuthToken(s *adlin.Student, _ httprouter.Params, _ []byte) (interface{}, error) { return s, nil } @@ -50,7 +50,7 @@ type loginForm struct { } func completeAuth(w http.ResponseWriter, username string, session *adlin.Session) (err error) { - var std adlin.Student + var std *adlin.Student if !adlin.StudentExists(username) { if std, err = adlin.NewStudent(username); err != nil { return err @@ -60,9 +60,7 @@ func completeAuth(w http.ResponseWriter, username string, session *adlin.Session } if session == nil { - var s adlin.Session - s, err = std.NewSession() - session = &s + session, err = std.NewSession() } else { _, err = session.SetStudent(std) } diff --git a/token-validator/auth_oidc.go b/token-validator/auth_oidc.go index 9118aea..590676a 100644 --- a/token-validator/auth_oidc.go +++ b/token-validator/auth_oidc.go @@ -111,7 +111,7 @@ func OIDC_CRI_complete(w http.ResponseWriter, r *http.Request, ps httprouter.Par return } - if err := completeAuth(w, claims.Username, &session); err != nil { + if err := completeAuth(w, claims.Username, session); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } diff --git a/token-validator/challenge.go b/token-validator/challenge.go index d21db5d..51144f1 100644 --- a/token-validator/challenge.go +++ b/token-validator/challenge.go @@ -338,7 +338,7 @@ func receiveChallenge(r *http.Request, ps httprouter.Params, body []byte) (inter return nil, errors.New("This is not the expected token.") } - var std adlin.Student + var std *adlin.Student if stdid, err := strconv.Atoi(gt.Login); err == nil { if std, err = adlin.GetStudent(stdid); err != nil { @@ -388,7 +388,7 @@ func receiveToken(r *http.Request, body []byte, chid int) (interface{}, error) { if std, err := adlin.GetStudentByLogin(gt.Login); err != nil { return nil, err } else { - if err := challenges[chid-1].Check(&std, >, chid); err != nil { + if err := challenges[chid-1].Check(std, >, chid); err != nil { log.Printf("%s just try ch#%d: %s\n", std.Login, chid, err) return nil, err } diff --git a/token-validator/check.go b/token-validator/check.go index 8e18faf..722e54f 100644 --- a/token-validator/check.go +++ b/token-validator/check.go @@ -19,7 +19,7 @@ type checkGLUE struct { } func init() { - router.POST("/api/check/GLUE", apiAuthHandler(func(student adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { + router.POST("/api/check/GLUE", apiAuthHandler(func(student *adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { var uc checkGLUE if err := json.Unmarshal(body, &uc); err != nil { return nil, err @@ -27,7 +27,7 @@ func init() { return true, check_GLUE_respond(student, uc.Domain, uc.IP) })) } -func check_GLUE_respond(student adlin.Student, domain string, ip string) (err error) { +func check_GLUE_respond(student *adlin.Student, domain string, ip string) (err error) { if !strings.HasPrefix(ip, adlin.StudentIP(student.Id).String()) { return fmt.Errorf("%q is not your IP range") } diff --git a/token-validator/domain.go b/token-validator/domain.go index 2497a72..596f3bd 100644 --- a/token-validator/domain.go +++ b/token-validator/domain.go @@ -22,103 +22,151 @@ var ( ) func init() { - router.GET("/api/adomains/", apiAuthHandler(func(student adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { + router.GET("/api/adomains/", apiAuthHandler(func(student *adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { return student.GetAssociatedDomains(), nil })) - router.POST("/api/adomains/", apiAuthHandler(func(student adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { + router.POST("/api/adomains/", apiAuthHandler(func(student *adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { ue := &struct { Domain string `json:"domain"` A string `json:"a"` AAAA string `json:"aaaa"` + CNAME string `json:"cname,omitempty"` }{} if err := json.Unmarshal(body, &ue); err != nil { return nil, err } - var aaaa net.IP - if ue != nil && len(ue.AAAA) > 0 { - aaaa = net.ParseIP(ue.AAAA) - } + if ue.Domain != "" && ue.A == "" && ue.AAAA == "" && ue.CNAME == "" { + student.AssociatedDomain = nil - return true, AddAssociatedDomains(student, aaaa) + if _, err := student.Update(); err != nil { + return nil, err + } + + return true, nil + } else if ue.CNAME != "" { + cname := dns.Fqdn(ue.CNAME) + student.AssociatedDomain = &cname + + if _, err := student.Update(); err != nil { + return nil, err + } + + return true, nil + } else { + var aaaa net.IP + if ue != nil && len(ue.AAAA) > 0 { + aaaa = net.ParseIP(ue.AAAA) + } + + return true, AddAssociatedDomains(student, aaaa) + } })) - router.GET("/api/adomains/:dn", apiAuthHandler(func(student adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { + router.GET("/api/adomains/:dn", apiAuthHandler(func(student *adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { return GetAssociatedDomain(student, ps.ByName("dn")) })) - router.GET("/api/ddomains/", apiAuthHandler(func(student adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { + router.GET("/api/ddomains/", apiAuthHandler(func(student *adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { return []string{student.MyDelegatedDomain()}, nil })) - router.GET("/api/ddomains/:dn/", apiAuthHandler(func(student adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { + router.POST("/api/ddomains/", apiAuthHandler(func(student *adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { + ue := &struct { + NS string `json:"ns"` + }{} + + if err := json.Unmarshal(body, &ue); err != nil { + return nil, err + } + + if ue.NS == "" { + student.DelegatedDomain = nil + + if _, err := student.Update(); err != nil { + return nil, err + } + + return true, nil + } else { + ns := dns.Fqdn(ue.NS) + student.DelegatedDomain = &ns + + if _, err := student.Update(); err != nil { + return nil, err + } + + return true, nil + } + })) + router.GET("/api/ddomains/:dn/", apiAuthHandler(func(student *adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { return getRRDelegatedDomain(student, ps.ByName("dn"), "") })) - router.GET("/api/ddomains/:dn/NS", apiAuthHandler(func(student adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { + router.GET("/api/ddomains/:dn/NS", apiAuthHandler(func(student *adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { return getRRDelegatedDomain(student, ps.ByName("dn"), "NS") })) - router.POST("/api/ddomains/:dn/NS", apiAuthHandler(func(student adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { + router.POST("/api/ddomains/:dn/NS", apiAuthHandler(func(student *adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { var ue Entry if err := json.Unmarshal(body, &ue); err != nil { return nil, err } return true, AddNSDelegatedDomain(student, ps.ByName("dn"), ue.TTL, strings.Join(ue.Values, " ")) })) - router.PATCH("/api/ddomains/:dn/NS", apiAuthHandler(func(student adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { + router.PATCH("/api/ddomains/:dn/NS", apiAuthHandler(func(student *adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { var ue Entry if err := json.Unmarshal(body, &ue); err != nil { return nil, err } return true, UpdateNSDelegatedDomain(student, ps.ByName("dn"), ue.TTL, ue.ValuesFrom, strings.Join(ue.Values, "")) })) - router.DELETE("/api/ddomains/:dn/NS", apiAuthHandler(func(student adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { + router.DELETE("/api/ddomains/:dn/NS", apiAuthHandler(func(student *adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { var ue Entry if err := json.Unmarshal(body, &ue); err != nil { return nil, err } return true, DeleteRRDelegatedDomain(student, ps.ByName("dn"), "NS", strings.Join(ue.Values, " ")) })) - router.GET("/api/ddomains/:dn/GLUE", apiAuthHandler(func(student adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { + router.GET("/api/ddomains/:dn/GLUE", apiAuthHandler(func(student *adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { return getRRDelegatedDomain(student, ps.ByName("dn"), "AAAA") })) - router.POST("/api/ddomains/:dn/AAAA", apiAuthHandler(func(student adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { + router.POST("/api/ddomains/:dn/AAAA", apiAuthHandler(func(student *adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { var ue Entry if err := json.Unmarshal(body, &ue); err != nil { return nil, err } return true, AddGLUEDelegatedDomain(student, ps.ByName("dn"), ue.TTL, strings.Join(ue.Values, " ")) })) - router.PATCH("/api/ddomains/:dn/AAAA", apiAuthHandler(func(student adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { + router.PATCH("/api/ddomains/:dn/AAAA", apiAuthHandler(func(student *adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { var ue Entry if err := json.Unmarshal(body, &ue); err != nil { return nil, err } return true, UpdateGLUEDelegatedDomain(student, ps.ByName("dn"), ue.TTL, ue.ValuesFrom, strings.Join(ue.Values, " ")) })) - router.POST("/api/ddomains/:dn/GLUE", apiAuthHandler(func(student adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { + router.POST("/api/ddomains/:dn/GLUE", apiAuthHandler(func(student *adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { var ue Entry if err := json.Unmarshal(body, &ue); err != nil { return nil, err } return true, UpdateGLUEDelegatedDomain(student, ps.ByName("dn"), ue.TTL, ue.ValuesFrom, strings.Join(ue.Values, " ")) })) - router.DELETE("/api/ddomains/:dn/AAAA", apiAuthHandler(func(student adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { + router.DELETE("/api/ddomains/:dn/AAAA", apiAuthHandler(func(student *adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { var ue Entry if err := json.Unmarshal(body, &ue); err != nil { return nil, err } return true, DeleteRRDelegatedDomain(student, ps.ByName("dn"), "AAAA", strings.Join(ue.Values, " ")) })) - router.GET("/api/ddomains/:dn/DS", apiAuthHandler(func(student adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { + router.GET("/api/ddomains/:dn/DS", apiAuthHandler(func(student *adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { return getRRDelegatedDomain(student, ps.ByName("dn"), "DS") })) - router.POST("/api/ddomains/:dn/DS", apiAuthHandler(func(student adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { + router.POST("/api/ddomains/:dn/DS", apiAuthHandler(func(student *adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { var ue Entry if err := json.Unmarshal(body, &ue); err != nil { return nil, err } return true, AddDSDelegatedDomain(student, ps.ByName("dn"), ue.TTL, strings.Join(ue.Values, " ")) })) - router.DELETE("/api/ddomains/:dn/DS", apiAuthHandler(func(student adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { + router.DELETE("/api/ddomains/:dn/DS", apiAuthHandler(func(student *adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { var ue Entry if err := json.Unmarshal(body, &ue); err != nil { return nil, err @@ -175,7 +223,7 @@ func parseZoneRead(globalDomain string, domain string) (rr []Entry, err error) { return } -func GetAssociatedDomain(student adlin.Student, dn string) (rrs []Entry, err error) { +func GetAssociatedDomain(student *adlin.Student, dn string) (rrs []Entry, err error) { domains := student.GetAssociatedDomains() found := false for _, d := range domains { @@ -201,7 +249,7 @@ func GetAssociatedDomain(student adlin.Student, dn string) (rrs []Entry, err err return } -func delAssociatedDomains(student adlin.Student, dn string) (err error) { +func delAssociatedDomains(student *adlin.Student, dn string) (err error) { var adomains []Entry adomains, err = GetAssociatedDomain(student, dn) if err != nil { @@ -238,15 +286,15 @@ func delAssociatedDomains(student adlin.Student, dn string) (err error) { return } -func AddAssociatedDomains(student adlin.Student, aaaa net.IP) (err error) { - err = delAssociatedDomains(student, student.MyAssociatedDomain()) +func AddAssociatedDomains(student *adlin.Student, aaaa net.IP) (err error) { + err = delAssociatedDomains(student, student.DefaultAssociatedDomain()) if err != nil { return } if aaaa == nil { aaaa = net.ParseIP(adlin.StudentIP(student.Id).String() + "1") - } else if !strings.HasPrefix(aaaa.String(), adlin.StudentIP(student.Id).String()) { + } else if !adlin.StudentNet(student.Id).Contains(aaaa) { return errors.New("The associated IP has to be in your IP range.") } @@ -257,12 +305,12 @@ func AddAssociatedDomains(student adlin.Student, aaaa net.IP) (err error) { m2.Question[0] = dns.Question{adlin.AssociatedDomainSuffix, dns.TypeSOA, dns.ClassINET} rrA := new(dns.A) - rrA.Hdr = dns.RR_Header{Name: student.MyAssociatedDomain(), Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: 3600} + rrA.Hdr = dns.RR_Header{Name: student.DefaultAssociatedDomain(), Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: 3600} rrA.A = net.IPv4(82, 64, 31, 248) m2.Insert([]dns.RR{rrA}) rrAAAA := new(dns.AAAA) - rrAAAA.Hdr = dns.RR_Header{Name: student.MyAssociatedDomain(), Rrtype: dns.TypeAAAA, Class: dns.ClassINET, Ttl: 3600} + rrAAAA.Hdr = dns.RR_Header{Name: student.DefaultAssociatedDomain(), Rrtype: dns.TypeAAAA, Class: dns.ClassINET, Ttl: 3600} rrAAAA.AAAA = aaaa m2.Insert([]dns.RR{rrAAAA}) @@ -274,7 +322,7 @@ func AddAssociatedDomains(student adlin.Student, aaaa net.IP) (err error) { return } -func getRRDelegatedDomain(student adlin.Student, dn string, rr string) (rrs []Entry, err error) { +func getRRDelegatedDomain(student *adlin.Student, dn string, rr string) (rrs []Entry, err error) { domains := []string{student.MyDelegatedDomain()} found := false for _, d := range domains { @@ -300,7 +348,7 @@ func getRRDelegatedDomain(student adlin.Student, dn string, rr string) (rrs []En return } -func AddNSDelegatedDomain(student adlin.Student, dn string, ttl uint32, ns string) (err error) { +func AddNSDelegatedDomain(student *adlin.Student, dn string, ttl uint32, ns string) (err error) { for _, d := range []string{student.MyDelegatedDomain()} { m1 := new(dns.Msg) m1.Id = dns.Id() @@ -323,7 +371,7 @@ func AddNSDelegatedDomain(student adlin.Student, dn string, ttl uint32, ns strin return } -func UpdateNSDelegatedDomain(student adlin.Student, dn string, ttl uint32, oldns string, ns string) (err error) { +func UpdateNSDelegatedDomain(student *adlin.Student, dn string, ttl uint32, oldns string, ns string) (err error) { for _, d := range []string{student.MyDelegatedDomain()} { m1 := new(dns.Msg) m1.Id = dns.Id() @@ -351,7 +399,7 @@ func UpdateNSDelegatedDomain(student adlin.Student, dn string, ttl uint32, oldns return } -func AddGLUEDelegatedDomain(student adlin.Student, dn string, ttl uint32, aaaa string) (err error) { +func AddGLUEDelegatedDomain(student *adlin.Student, dn string, ttl uint32, aaaa string) (err error) { domains := []string{student.MyDelegatedDomain()} found := false for _, d := range domains { @@ -387,7 +435,7 @@ func AddGLUEDelegatedDomain(student adlin.Student, dn string, ttl uint32, aaaa s return } -func UpdateGLUEDelegatedDomain(student adlin.Student, dn string, ttl uint32, oldaaaa string, aaaa string) (err error) { +func UpdateGLUEDelegatedDomain(student *adlin.Student, dn string, ttl uint32, oldaaaa string, aaaa string) (err error) { domains := []string{student.MyDelegatedDomain()} found := false for _, d := range domains { @@ -429,7 +477,7 @@ func UpdateGLUEDelegatedDomain(student adlin.Student, dn string, ttl uint32, old return } -func AddDSDelegatedDomain(student adlin.Student, dn string, ttl uint32, rdata string) (err error) { +func AddDSDelegatedDomain(student *adlin.Student, dn string, ttl uint32, rdata string) (err error) { domains := []string{student.MyDelegatedDomain()} found := false for _, d := range domains { @@ -479,7 +527,7 @@ func AddDSDelegatedDomain(student adlin.Student, dn string, ttl uint32, rdata st return } -func DeleteRRDelegatedDomain(student adlin.Student, dn string, rr string, values ...string) (err error) { +func DeleteRRDelegatedDomain(student *adlin.Student, dn string, rr string, values ...string) (err error) { domains := []string{student.MyDelegatedDomain()} found := false for _, d := range domains { diff --git a/token-validator/handler.go b/token-validator/handler.go index c93c842..d40be85 100644 --- a/token-validator/handler.go +++ b/token-validator/handler.go @@ -65,7 +65,7 @@ func rawHandler(f func(http.ResponseWriter, *http.Request, httprouter.Params, [] http.Error(w, fmt.Sprintf(`{"errmsg": %q}`, err), http.StatusUnauthorized) return } else { - student = &std + student = std } } @@ -158,7 +158,7 @@ func apiHandler(f DispatchFunction, access ...func(*adlin.Student, *http.Request return rawHandler(responseHandler(func(_ *http.Request, ps httprouter.Params, b []byte) (interface{}, error) { return f(ps, b) }), access...) } -func apiAuthHandler(f func(adlin.Student, httprouter.Params, []byte) (interface{}, error), access ...func(*adlin.Student, *http.Request) error) func(http.ResponseWriter, *http.Request, httprouter.Params) { +func apiAuthHandler(f func(*adlin.Student, httprouter.Params, []byte) (interface{}, error), access ...func(*adlin.Student, *http.Request) error) func(http.ResponseWriter, *http.Request, httprouter.Params) { return rawHandler(responseHandler(func(r *http.Request, ps httprouter.Params, b []byte) (interface{}, error) { if cookie, err := r.Cookie("auth"); err != nil { return nil, errors.New("Authorization required") @@ -176,7 +176,7 @@ func apiAuthHandler(f func(adlin.Student, httprouter.Params, []byte) (interface{ }), access...) } -func studentHandler(f func(adlin.Student, []byte) (interface{}, error)) func(httprouter.Params, []byte) (interface{}, error) { +func studentHandler(f func(*adlin.Student, []byte) (interface{}, error)) func(httprouter.Params, []byte) (interface{}, error) { return func(ps httprouter.Params, body []byte) (interface{}, error) { if sid, err := strconv.Atoi(string(ps.ByName("sid"))); err != nil { if student, err := adlin.GetStudentByLogin(ps.ByName("sid")); err != nil { diff --git a/token-validator/htdocs/dashboard.html b/token-validator/htdocs/dashboard.html index 8815e14..8783eb6 100644 --- a/token-validator/htdocs/dashboard.html +++ b/token-validator/htdocs/dashboard.html @@ -24,7 +24,7 @@ height: calc(100vh / 5 - 0.3vh); margin-left: 0.2%; margin-bottom: 0.2vh; - width: calc(100vw / 13 - 0.22vw); + width: calc(100vw / 13 - 0.26vw); } .student-title { width: calc(2 * (100vw / 13 - 0.22vw) + 0.2vw); @@ -47,7 +47,7 @@ .login { max-width: 122px; } - .badge { + .badge.badge-sm { font-size: 59%; padding: .15em .35em; } @@ -72,12 +72,12 @@

TP {{tutoid+1}}
- +

@@ -108,7 +108,7 @@ -
+
- +
@@ -126,6 +126,7 @@ + diff --git a/token-validator/htdocs/js/adlin-common.js b/token-validator/htdocs/js/adlin-common.js new file mode 100644 index 0000000..a6e4f55 --- /dev/null +++ b/token-validator/htdocs/js/adlin-common.js @@ -0,0 +1,28 @@ +var tuto_progress = [ + { + 1: { title: "Is alive?", icon: "👋", label: "Token 1"}, + 2: { title: "DMZ reached", icon: "📚", label: "Token 2"}, + 3: { title: "HTTPS on + time", icon: "⏲", label: "Token 3"}, + 4: { title: "DNS ok", icon: "🍰", label: "Token 4"}, + 5: { title: "On Internet", icon: "🌎", label: "Token 5"}, + 6: { title: "Bonus caché", icon: "b", label: "Bonus 0"}, + 7: { title: "Bonus ICMP", icon: "🏓", label: "Bonus 1"}, + 8: { title: "Bonus disk", icon: "💽", label: "Bonus 2"}, + 9: { title: "Bonus email", icon: "📧", label: "Bonus 3"}, + 10: { title: "Wg tunnel", icon: "🚇", label: "Tunnel up"}, + 11: { title: "Uploaded SSH key", icon: "💊", label: "SSH"}, + }, + { + 100: { title: "HTTP on IP", icon: "0", label: "HTTP IP"}, + 101: { title: "HTTP on associated domain", icon: "1", label: "HTTP domain"}, + 102: { title: "HTTPS on associated domain", icon: "2", label: "HTTPS domain"}, + 103: { title: "DNS Delegation", icon: "3", label: "DNS"}, + 104: { title: "HTTP on delegated domain", icon: "4", label: "HTTP on NS"}, + 105: { title: "HTTPS on delegated domain", icon: "5", label: "HTTPS on NS"}, + 106: { title: "Matrix", icon: "6", label: "Matrix"}, + 107: { title: "DNSSEC (bonus)", icon: "7", label: "DNSSEC"}, + }, + { + 200: { title: "HTTP", label: "HTTP"}, + }, +]; diff --git a/token-validator/htdocs/js/adlin-dashboard.js b/token-validator/htdocs/js/adlin-dashboard.js index baee842..b0fd719 100644 --- a/token-validator/htdocs/js/adlin-dashboard.js +++ b/token-validator/htdocs/js/adlin-dashboard.js @@ -1,32 +1,3 @@ -var tuto_progress = [ - { - 1: { title: "Is alive?", label: "👋"}, - 2: { title: "DMZ reached", label: "📚"}, - 3: { title: "HTTPS on + time", label: "⏲"}, - 4: { title: "DNS ok", label: "🍰"}, - 5: { title: "On Internet", label: "🌎"}, - 6: { title: "Bonus caché", label: "b"}, - 7: { title: "Bonus ICMP", label: "🏓"}, - 8: { title: "Bonus disk", label: "💽"}, - 9: { title: "Bonus email", label: "📧"}, - 10: { title: "Wg tunnel", label: "🚇"}, - 11: { title: "Uploaded SSH key", label: "💊"}, - }, - { - 100: { title: "HTTP", label: "HTTP"}, - 101: { title: "HTTPS", label: "HTTPS"}, - 102: { title: "DNS", label: "DNS"}, - 103: { title: "Matrix", label: "Matrix"}, - }, - { - 200: { title: "HTTP", label: "HTTP"}, - 201: { title: "HTTPS", label: "HTTPS"}, - 202: { title: "DNS", label: "DNS"}, - 203: { title: "Matrix", label: "Matrix"}, - }, -]; - - angular.module("AdLinApp", ["ngResource", "ngSanitize"]) .factory("Student", function($resource) { return $resource("/api/students/:studentId", { studentId: '@id' }, { @@ -65,7 +36,8 @@ angular.module("AdLinApp") var refreshStd = function() { $scope.students = Student.query(); } - $interval(refreshStd, 1600000); + var myinterval = $interval(refreshStd, 1600000); + $scope.$on('$destroy', function () { $interval.cancel(myinterval); }); }) .controller("StudentsProgressionController", function($scope, $interval, Progression) { $scope.tuto_progress = tuto_progress; @@ -109,7 +81,8 @@ angular.module("AdLinApp") }) } refreshStd(); - $interval(refreshStd, 9750); + var myinterval = $interval(refreshStd, 9750); + $scope.$on('$destroy', function () { $interval.cancel(myinterval); }); }) .controller("StudentProgressionController", function($scope, $interval, $http, Student, StudentProgression) { $scope.tuto_progress = tuto_progress; @@ -133,7 +106,8 @@ angular.module("AdLinApp") } $scope.$watch("onestudent", function(onestudent) { refreshStd(); - $interval(refreshStd, 15000); + var myinterval = $interval(refreshStd, 15000); + $scope.$on('$destroy', function () { $interval.cancel(myinterval); }); }) }) .controller("PingController", function($scope, $interval, $http) { @@ -150,7 +124,8 @@ angular.module("AdLinApp") $scope.$watch("student", function(student) { student.$promise.then(function(std) { refreshPing(); - $interval(refreshPing, 15000); + var myinterval = $interval(refreshPing, 15000); + $scope.$on('$destroy', function () { $interval.cancel(myinterval); }); }) }) }) @@ -162,7 +137,8 @@ angular.module("AdLinApp") }); } refreshSSH(); - $interval(refreshSSH, 15500); + var myinterval = $interval(refreshSSH, 15500); + $scope.$on('$destroy', function () { $interval.cancel(myinterval); }); }) .controller("ProgressionController", function($scope, $interval, $http) { $scope.tuto_progress = tuto_progress; @@ -179,5 +155,6 @@ angular.module("AdLinApp") }); } refreshChal(); - $interval(refreshChal, 15750); + var myinterval = $interval(refreshChal, 15750); + $scope.$on('$destroy', function () { $interval.cancel(myinterval); }); }) diff --git a/token-validator/htdocs/js/adlin-main.js b/token-validator/htdocs/js/adlin-main.js index bdf72f9..05908d3 100644 --- a/token-validator/htdocs/js/adlin-main.js +++ b/token-validator/htdocs/js/adlin-main.js @@ -44,6 +44,17 @@ angular.module("AdLinApp") } }) + .directive('integer', function() { + return { + require: 'ngModel', + link: function(scope, ele, attr, ctrl){ + ctrl.$parsers.unshift(function(viewValue){ + return parseInt(viewValue, 10); + }); + } + }; + }) + .component('toast', { bindings: { date: '=', @@ -100,6 +111,8 @@ angular.module("AdLinApp") }) .controller("ProgressionController", function($scope, $interval, $http) { + $scope.tuto_progress = tuto_progress; + $scope.mychallenges = {}; var refreshChal = function() { $http.get("api/students/" + $scope.student.id + "/progress").then(function(response) { @@ -150,7 +163,10 @@ angular.module("AdLinApp") }; $scope.updateTunnelInfo(); - $scope.updateTunnelsList = function() { + var noUpdate = 0 + + $scope.updateTunnelsList = function() { + if (noUpdate == 0) $http({ method: 'GET', url: "api/wg/", @@ -211,11 +227,48 @@ angular.module("AdLinApp") }); } + $scope.editTunnel = function(tunnel) { + tunnel.edit = true; + noUpdate++; + tunnel.newData = { + TokenText: tunnel.TokenText, + SuffixIP: tunnel.SuffixIP, + } + }; + + $scope.updateTunnel = function(tunnel) { + tunnel.pleaseWaitUpdate = true; + $http({ + method: 'PUT', + url: "api/wg/" + encodeURIComponent(tunnel.TokenText), + data: tunnel.newData + }).then(function(response) { + noUpdate--; + tunnel.SuffixIP = tunnel.newData.SuffixIP; + tunnel.TokenText = tunnel.newData.TokenText; + tunnel.edit = false; + tunnel.pleaseWaitUpdate = false; + $scope.updateTunnelsList(); + $scope.addToast({ + variant: "success", + title: "Maatma Tunnels", + msg: "Tunnel mise à jour avec succès !", + }); + }, function(response) { + tunnel.pleaseWaitUpdate = false; + $scope.addToast({ + variant: "danger", + title: "Maatma Tunnels", + msg: (response.data ? response.data.errmsg : "Impossible de contacter le serveur"), + }); + }); + } + $scope.dropTunnel = function(tunnel) { tunnel.pleaseWaitDrop = true; $http({ method: 'DELETE', - url: "api/wg/" + tunnel.TokenText, + url: "api/wg/" + encodeURIComponent(tunnel.TokenText), data: {} }).then(function(response) { $scope.updateTunnelsList(); @@ -330,6 +383,123 @@ angular.module("AdLinApp") }); } + $scope.useMyAssociationD = function() { + $scope.assoc = { + "domain": $scope.adomains[0].domain, + "cname": $scope.student.associated_domain?$scope.student.associated_domain:"", + } + $('#AssocMyDomainModal').modal('show'); + } + + $scope.newMyDomainAssociationD = function(assoc) { + $('#AssocMyDomainModal').modal('hide'); + $scope.pleaseWaitNewAssociation = true; + $http({ + method: 'POST', + url: "api/adomains/", + data: assoc, + }).then(function(response) { + $scope.updateAssociationD(); + $scope.checkLoginState(); + $scope.pleaseWaitNewAssociation = false; + $scope.addToast({ + variant: "success", + title: "Maatma Domain Names", + msg: "Votre domaine a bien été associé !", + }); + }, function(response) { + $scope.pleaseWaitNewAssociation = false; + $scope.addToast({ + variant: "danger", + title: "Maatma Domain Names", + msg: "Erreur durant l'association du domaine : " + response.data.errmsg, + }); + }); + } + + $scope.delMyDomainAssociationD = function(assoc) { + $('#AssocMyDomainModal').modal('hide'); + $scope.pleaseWaitNewAssociation = true; + assoc.cname = '' + $http({ + method: 'POST', + url: "api/adomains/", + data: assoc, + }).then(function(response) { + $scope.updateAssociationD(); + $scope.checkLoginState(); + $scope.pleaseWaitNewAssociation = false; + $scope.addToast({ + variant: "success", + title: "Maatma Domain Names", + msg: "Votre domaine n'est plus pris en compte. Vous devez utiliser l'association qui vous a été attribuée sous adlin20xx.p0m.fr.", + }); + }, function(response) { + $scope.pleaseWaitNewAssociation = false; + $scope.addToast({ + variant: "danger", + title: "Maatma Domain Names", + msg: "Erreur durant l'association du domaine : " + response.data.errmsg, + }); + }); + } + + $scope.useMyDelegationD = function() { + $scope.assoc = { + "ns": $scope.student.delegated_domain?$scope.student.delegated_domain:"", + } + $('#DelegateMyDomainModal').modal('show'); + } + + $scope.newMyDomainDelegationD = function(assoc) { + $('#DelegateMyDomainModal').modal('hide'); + $scope.pleaseWaitNewDelegation = true; + $http({ + method: 'POST', + url: "api/ddomains/", + data: assoc, + }).then(function(response) { + $scope.checkLoginState(); + $scope.pleaseWaitNewDelegation = false; + $scope.addToast({ + variant: "success", + title: "Maatma Domain Names", + msg: "Votre sous-domaine de délégation a bien été enregistré !", + }); + }, function(response) { + $scope.pleaseWaitNewDelegation = false; + $scope.addToast({ + variant: "danger", + title: "Maatma Domain Names", + msg: "Erreur durant la délégation du domaine : " + response.data.errmsg, + }); + }); + } + + $scope.delMyDomainDelegatedD = function() { + $scope.pleaseWaitNewDelegation = true; + $http({ + method: 'POST', + url: "api/ddomains/", + data: {}, + }).then(function(response) { + $scope.checkLoginState(); + $scope.pleaseWaitNewDelegation = false; + $scope.addToast({ + variant: "success", + title: "Maatma Domain Names", + msg: "Votre domaine n'est plus pris en compte. Vous devez utiliser la délégation qui vous a été attribuée sous srs.p0m.fr.", + }); + }, function(response) { + $scope.pleaseWaitNewDelegation = false; + $scope.addToast({ + variant: "danger", + title: "Maatma Domain Names", + msg: "Erreur durant la délégation du domaine : " + response.data.errmsg, + }); + }); + } + $scope.addNS = function(domain) { $scope.nsrr = { "domain": domain, diff --git a/token-validator/htdocs/maatma.html b/token-validator/htdocs/maatma.html index 2128f2f..67cc023 100644 --- a/token-validator/htdocs/maatma.html +++ b/token-validator/htdocs/maatma.html @@ -46,6 +46,7 @@ + diff --git a/token-validator/htdocs/views/domains.html b/token-validator/htdocs/views/domains.html index cd2161a..fe02e77 100644 --- a/token-validator/htdocs/views/domains.html +++ b/token-validator/htdocs/views/domains.html @@ -2,7 +2,10 @@ Noms de domaine -

Association simple

+

+ Association simple + ? +

@@ -28,6 +31,9 @@ Demander une nouvelle association + @@ -35,7 +41,10 @@
-

Délégation

+

+ Délégation + ? +

-
+
-

{{ domain }}

+

+ {{ domain }} + +

@@ -65,7 +80,7 @@ - - - + + + + + + + - +
{{ val }} - Not implemented yet + @@ -180,7 +195,31 @@ +
+

+ {{ student.delegated_domain }} + +

+

+ Vous avez choisi d'utiliser votre propre domaine pour réaliser la délégation. +

+

+ L'interface de maatma ne vous est plus utile, car pour réaliser la délégation, vous devez passer par l'interface de votre bureau d'enregistrement. +

+

+ Pour rappel, voici les enregistrements à rajouter : +

+
+;; Delegation {{ student.delegated_domain }} to the given name server
+{{ student.delegated_domain }} 300 IN NS ns.{{ student.delegated_domain }}
 
+;; GLUE record to serve along with the previous record
+ns.{{ student.delegated_domain }} 300 IN AAAA [your NS ip]
+  
+
+ + + +
TokenDernière utilisationClef publique + Token + ? + + Suffix + ? + + Dernière utilisation + ? + + Clef publique + ? +
- > - + > + {{ tunnel.TokenText }} + + {{ tunnel.SuffixIP }}Par défaut {{ tunnel.Time | date:"medium" }} (VM TP {{ tunnel.Version }}){{ tunnel.PubKey }}(none){{ tunnel.PubKey }}(none) - + + @@ -42,7 +67,10 @@
-

Paramètres du tunnel

+

+ Paramètres du tunnel + ? +

  • Statut : {{ wginfo.status }}
  • Clef publique du serveur : {{ wginfo.srv_pubkey }}
  • @@ -51,10 +79,11 @@
  • Gateway/passerelle IPv6 : {{ wginfo.srv_gw6 }}
-
+

État de mon tunnel - 💻 + 💻 + ?

  • Clef publique pair : {{ tunnel.Dump.PubKey }}
  • diff --git a/token-validator/ip.go b/token-validator/ip.go index aca9a79..885cd0f 100644 --- a/token-validator/ip.go +++ b/token-validator/ip.go @@ -3,6 +3,7 @@ package main import ( "fmt" "net" + "strconv" "github.com/julienschmidt/httprouter" @@ -11,12 +12,12 @@ import ( func init() { router.GET("/api/ips", apiHandler(showIPs)) - router.GET("/api/students/:sid/ips", apiHandler(studentHandler(func(student adlin.Student, body []byte) (interface{}, error) { + router.GET("/api/students/:sid/ips", apiHandler(studentHandler(func(student *adlin.Student, body []byte) (interface{}, error) { return getStudentIPs(student), nil }))) } -func IPSuffix(s adlin.Student, network net.IPNet) net.IP { +func IPSuffix(s *adlin.Student, network net.IPNet) net.IP { ipshift := s.Id*4 + 10 myIP := network.IP @@ -54,13 +55,32 @@ func showIPs(_ httprouter.Params, body []byte) (interface{}, error) { return r, nil } -func getStudentIPs(student adlin.Student) (r map[string]string) { +func GetStudentTunnelIPs(student *adlin.Student) (ips []string) { + if ts, err := student.GetActivesTunnels(); err != nil || len(ts) == 0 || ts[0].SuffixIP == 0 { + ips = append(ips, adlin.StudentIP(student.Id).String()+"1") + } else { + for _, t := range ts { + ips = append(ips, t.GetStudentIP()) + } + } + return +} + +func getStudentIPs(student *adlin.Student) (r map[string]string) { r = make(map[string]string) r["vlan0"] = IPSuffix(student, net.IPNet{net.ParseIP("172.23.0.0"), net.CIDRMask(17, 32)}).String() r["wg0"] = IPSuffix(student, net.IPNet{net.ParseIP("172.17.0.0"), net.CIDRMask(16, 32)}).String() r["vlan7"] = IPSuffix(student, net.IPNet{net.ParseIP("172.23.142.0"), net.CIDRMask(23, 32)}).String() - r["wg"] = adlin.StudentIP(student.Id).String() + + for d, ip := range GetStudentTunnelIPs(student) { + key := "wg" + if d > 0 { + key += strconv.Itoa(d) + } + r[key] = ip + } + r["adn"] = student.MyAssociatedDomain() r["ddn"] = student.MyDelegatedDomain() diff --git a/token-validator/ping.go b/token-validator/ping.go index b6441c5..37a6290 100644 --- a/token-validator/ping.go +++ b/token-validator/ping.go @@ -11,13 +11,13 @@ var PongSecret = "felixfixit" func init() { router.GET("/api/students/:sid/ping", apiHandler(studentHandler(lastPing))) - router.GET("/api/students/:sid/pong", apiHandler(studentHandler(func(student adlin.Student, body []byte) (interface{}, error) { + router.GET("/api/students/:sid/pong", apiHandler(studentHandler(func(student *adlin.Student, body []byte) (interface{}, error) { return student.LastPongs() }))) router.POST("/api/students/:sid/pong", apiHandler(studentHandler(stdPong), sslOnly)) } -func lastPing(student adlin.Student, body []byte) (interface{}, error) { +func lastPing(student *adlin.Student, body []byte) (interface{}, error) { if pongs, err := student.LastPongs(); err != nil { return nil, err } else if len(pongs) <= 0 { @@ -27,7 +27,7 @@ func lastPing(student adlin.Student, body []byte) (interface{}, error) { } } -func stdPong(student adlin.Student, body []byte) (interface{}, error) { +func stdPong(student *adlin.Student, body []byte) (interface{}, error) { var gt givenToken if err := json.Unmarshal(body, >); err != nil { return nil, err diff --git a/token-validator/ssh.go b/token-validator/ssh.go index 666db4c..f810571 100644 --- a/token-validator/ssh.go +++ b/token-validator/ssh.go @@ -47,7 +47,7 @@ func init() { }) } -func hasSSHKeys(student adlin.Student, body []byte) (interface{}, error) { +func hasSSHKeys(student *adlin.Student, body []byte) (interface{}, error) { if keys, err := student.GetKeys(); err != nil { return nil, err } else { @@ -141,7 +141,7 @@ func dumpAuthorizedKeysFile(w io.Writer) { } } -func dumpStdAuthorizedKeysFile(s adlin.Student, w io.Writer) { +func dumpStdAuthorizedKeysFile(s *adlin.Student, w io.Writer) { seen := map[string]interface{}{} if keys, _ := s.GetKeys(); keys != nil { diff --git a/token-validator/students.go b/token-validator/students.go index ac101fa..cee1e47 100644 --- a/token-validator/students.go +++ b/token-validator/students.go @@ -18,19 +18,19 @@ func init() { if stds, err := adlin.GetStudents(); err != nil { return nil, err } else { - ret := map[string]map[string]adlin.UnlockedChallenge{} + ret := map[string]map[string]*adlin.UnlockedChallenge{} for _, std := range stds { if sts, err := std.GetStates(); err == nil { - ret[std.Login] = map[string]adlin.UnlockedChallenge{} + ret[std.Login] = map[string]*adlin.UnlockedChallenge{} for _, s := range sts { ret[std.Login][fmt.Sprintf("%d", s.Challenge)] = s } if pongs, err := std.LastPongs(); err == nil && len(pongs) > 0 { - ret[std.Login]["ping"] = adlin.UnlockedChallenge{ + ret[std.Login]["ping"] = &adlin.UnlockedChallenge{ IdStudent: std.Id, - Time: pongs[0].Date, + Time: &pongs[0].Date, Value: pongs[0].State, } } else if err != nil { @@ -47,17 +47,17 @@ func init() { })) router.POST("/api/students/", remoteValidatorHandler(apiHandler(createStudent))) router.GET("/api/students/:sid/", apiHandler(studentHandler( - func(std adlin.Student, _ []byte) (interface{}, error) { + func(std *adlin.Student, _ []byte) (interface{}, error) { return std, nil }))) router.PUT("/api/students/:sid/", remoteValidatorHandler(apiHandler(studentHandler(updateStudent)))) router.DELETE("/api/students/:sid/", remoteValidatorHandler(apiHandler(studentHandler( - func(std adlin.Student, _ []byte) (interface{}, error) { + func(std *adlin.Student, _ []byte) (interface{}, error) { return std.Delete() })))) router.GET("/api/students/:sid/progress", apiHandler(studentHandler( - func(std adlin.Student, _ []byte) (interface{}, error) { - ret := map[string]adlin.UnlockedChallenge{} + func(std *adlin.Student, _ []byte) (interface{}, error) { + ret := map[string]*adlin.UnlockedChallenge{} if sts, err := std.GetStates(); err == nil { for _, s := range sts { @@ -65,6 +65,20 @@ func init() { } } + if cerrors, err := std.GetChallengeErrors(); err == nil { + for _, cerr := range cerrors { + if _, ok := ret[fmt.Sprintf("%d", cerr.Challenge)]; ok { + ret[fmt.Sprintf("%d", cerr.Challenge)].Error = cerr.Error + ret[fmt.Sprintf("%d", cerr.Challenge)].LastCheck = &cerr.Time + } else { + ret[fmt.Sprintf("%d", cerr.Challenge)] = &adlin.UnlockedChallenge{ + LastCheck: &cerr.Time, + Error: cerr.Error, + } + } + } + } + return ret, nil }))) } @@ -82,7 +96,7 @@ func createStudent(_ httprouter.Params, body []byte) (interface{}, error) { return nil, err } - var exist adlin.Student + var exist *adlin.Student if exist, err = adlin.GetStudentByLogin(strings.TrimSpace(std.Login)); err != nil { if exist, err = adlin.NewStudent(strings.TrimSpace(std.Login)); err != nil { return nil, err @@ -96,8 +110,8 @@ func createStudent(_ httprouter.Params, body []byte) (interface{}, error) { return exist, nil } -func updateStudent(current adlin.Student, body []byte) (interface{}, error) { - var new adlin.Student +func updateStudent(current *adlin.Student, body []byte) (interface{}, error) { + new := &adlin.Student{} if err := json.Unmarshal(body, &new); err != nil { return nil, err } diff --git a/token-validator/wg.go b/token-validator/wg.go index 544dd81..195a3f7 100644 --- a/token-validator/wg.go +++ b/token-validator/wg.go @@ -27,20 +27,22 @@ func init() { } }) router.GET("/api/wg/", apiAuthHandler(showWgTunnel)) - router.GET("/api/wginfo", apiAuthHandler(func(student adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { + router.GET("/api/wginfo", apiAuthHandler(func(student *adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { return getTunnelInfo(student.Id), nil })) router.POST("/api/wg/", apiAuthHandler(genWgToken)) router.GET("/api/wg/:token", getWgTunnelInfo) router.POST("/api/wg/:token", getWgTunnelInfo) + router.PUT("/api/wg/:token", apiAuthHandler(updateWgTunnel)) + router.DELETE("/api/wg/:token", apiAuthHandler(deleteWgTunnel)) } -func showWgTunnel(student adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { +func showWgTunnel(student *adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { // Get tunnels assigned to the student return student.GetTunnelTokens() } -func genWgToken(student adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { +func genWgToken(student *adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { // Generate a token to access related wg info return student.NewTunnelToken(0) } @@ -61,7 +63,7 @@ func getTunnelInfo(student int64) TunnelInfo { SrvPubKey: srv_pubkey, SrvPort: 42912, CltIPv6: adlin.StudentIP(student), - CltRange: 80, + CltRange: adlin.StdNetmask, SrvGW6: "2a01:e0a:2b:2252::1", } } @@ -121,11 +123,16 @@ func getWgTunnelInfo(w http.ResponseWriter, r *http.Request, ps httprouter.Param return } + // 0 is considered default for suffix, apply default now + if token.SuffixIP <= 0 { + token.SuffixIP = 1 + } + syncWgConf() tinfo := getTunnelInfo(token.IdStudent) - var student adlin.Student + var student *adlin.Student student, err = adlin.GetStudent(int(token.IdStudent)) if err != nil { http.Error(w, fmt.Sprintf("{errmsg:%q}", err), http.StatusBadRequest) @@ -138,11 +145,58 @@ PublicKey = %s Endpoint = %s:%d AllowedIPs = ::/0 PersistentKeepalive = 5 -# MyIPv6=%s1/%d +# MyIPv6=%s%x/%d # MyNetwork=%s/%d # GWIPv6=%s # MyLogin=%s -`, base64.StdEncoding.EncodeToString(tinfo.SrvPubKey), "82.64.31.248", tinfo.SrvPort, tinfo.CltIPv6, 64, tinfo.CltIPv6, tinfo.CltRange, tinfo.SrvGW6, student.Login))) +`, base64.StdEncoding.EncodeToString(tinfo.SrvPubKey), "82.64.31.248", tinfo.SrvPort, tinfo.CltIPv6, token.SuffixIP, 64, tinfo.CltIPv6, tinfo.CltRange, tinfo.SrvGW6, student.Login))) +} + +func updateWgTunnel(student *adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { + token, err := adlin.GetTunnelToken(adlin.TokenFromText(ps.ByName("token"))) + if err != nil { + return nil, err + } + + if token.IdStudent != student.Id { + return nil, fmt.Errorf("Unauthorized") + } + + var newToken adlin.TunnelToken + if err := json.Unmarshal(body, &newToken); err != nil { + return nil, err + } + + token.TokenText = newToken.TokenText + token.PubKey = newToken.PubKey + token.SuffixIP = newToken.SuffixIP + + if _, err = token.Update(); err != nil { + return nil, err + } + + syncWgConf() + + return true, err +} + +func deleteWgTunnel(student *adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { + token, err := adlin.GetTunnelToken(adlin.TokenFromText(ps.ByName("token"))) + if err != nil { + return nil, err + } + + if token.IdStudent != student.Id { + return nil, fmt.Errorf("Unauthorized") + } + + if _, err = token.Delete(); err != nil { + return nil, err + } + + syncWgConf() + + return true, err } func GenWGConfig(w io.Writer) error { diff --git a/tuto2.yml b/tuto2.yml index efb3ebf..bcd1223 100644 --- a/tuto2.yml +++ b/tuto2.yml @@ -1,9 +1,9 @@ kernel: - image: linuxkit/kernel:4.19.104 + image: linuxkit/kernel:4.19.121 cmdline: "console=tty0 console=ttyS0 root=/dev/sda1 root=/dev/sr0 adlin.format=/dev/sda quiet" init: - - nemunaire/adlin-tuto2:41e341472a4a1b27dcf61c7d364f1f0a5f76fbe7 + - nemunaire/adlin-tuto2:a68d5f224331628dc525edf383ec7429dfe001b0 files: - path: etc/hostname @@ -15,7 +15,7 @@ files: - path: etc/resolv.conf contents: | - nameserver 9.9.9.9 + nameserver 9.9.9.10 nameserver 1.1.1.1 uid: 0 gid: 0 @@ -154,6 +154,14 @@ files: /bin/ip -6 route del default /bin/ip -6 route add default via $(sed 's/^.*GWIPv6=//p;d' etc/wireguard/adlin.conf) pref high + # Download intermediate fixes + curl -s -f -H "X-ADLIN-time: $(stat -c %Y /boot)" https://adlin.nemunai.re/fix-vm2 | sh + + # Retrieve ssh keys + mkdir -p root/.ssh/ + [ -f root/.ssh/authorized_keys ] || /usr/sbin/chroot . /usr/bin/curl -s -f https://cri.epita.fr/$(sed 's/^.*MyLogin=//p;d' etc/wireguard/adlin.conf).keys > root/.ssh/authorized_keys + [ -f etc/ssh/ssh_host_rsa_key ] || /usr/sbin/chroot . ssh-keygen -A + # To the user exec /usr/sbin/chroot . "${INITP}" uid: 0 @@ -170,7 +178,7 @@ files: - path: etc/shadow contents: | - root:$6$QNuPvO59Xk4UO3le$3P0V2ef6dHlKgO1FHsKcPPgOvL.YeCOPFqfIVTtpYn5eEn3xkgGYeM1RMCQ9l/eTc6rRc.l.WeRe1iJVznVGj/:18336:0:99999:7::: + root:$6$dQXVLB.662ob0XJL$wRhh73Q.Z3mBRHhM0rSw96dE0bOFykfIXa2Z2ncu6WVSOpFLdv5J6Br9AHhalO4wwG3xgPqqhvCdEMdroR2r50:18336:0:99999:7::: daemon:*:18316:0:99999:7::: bin:*:18316:0:99999:7::: sys:*:18316:0:99999:7::: diff --git a/tutorial/ansible/ansible.md b/tutorial/ansible/ansible.md index 924bafd..be65bfb 100644 --- a/tutorial/ansible/ansible.md +++ b/tutorial/ansible/ansible.md @@ -20,7 +20,9 @@ Ansible est une solution de gestion de configuration. Basé sur [YAML](http://www.yaml.org/spec/1.2/spec.html), sa principale particularité est de ne pas nécessité de daemon sur les machines qu'il va gérer : tout se fait exclusivement via SSH, à partir de la machine d'un administrateur, possédant -Ansible. +Ansible (ou bien d'un système de gestion de configuration tel qu'[Ansible +Tower](https://www.ansible.com/products/tower) ou +[AWX](https://github.com/ansible/awx)). Son installation est très simple, car les dépendances sont minimes et l'outil n'a pas besoin de base de données pour fonctionner : tout va se faire à partir @@ -48,7 +50,7 @@ système et des utilisateurs. Un deuxième playbook est à rendre : `login-x-TP2/vitrine.yml`, celui-ci doit permettre de déployer (en parallèle de tous les autres), une page vitrine typique d'une entreprise (cf. la 4e question de cours ;)). Cette page doit être -accessible depuis votre domaine . +accessible depuis votre domaine . Mon première commande @@ -85,7 +87,7 @@ Lancez ensuite la commande suivante :
    ``` -42sh$ ansible --inventory-file hosts all --module-name ping --user root --ask-pass +42sh$ ansible --inventory-file hosts all --module-name ping --user root 192.168.0.106 | SUCCESS => { "changed": false, "ping": "pong" @@ -97,6 +99,10 @@ Vous devriez avoir un retour similaire à celui-ci, indiquant simplement que la connexion a bien été effectuée et que le nécessaire est bien installé sur la machine distance. +Si votre clef SSH n'a pas été récupérée depuis le CRI, vous pouvez rajouter +l'option `--ask-pass`, afin de pouvoir indiquer le mot de passe de connexion au +compte, sinon vous obtiendrez une erreur plutôt incrompréhensible. + ### Confort @@ -281,7 +287,7 @@ pour davantage de détails et d'exemples. La configuration de votre serveur SSH laisse à désirer. Corriger les problèmes énoncés par ces deux articles : --  ; +-  ; - . Mettez en place un *handler* pour relancer votre serveur SSH en cas de diff --git a/tutorial/ansible/deploiement-svc.md b/tutorial/ansible/deploiement-svc.md index b8295f0..953725c 100644 --- a/tutorial/ansible/deploiement-svc.md +++ b/tutorial/ansible/deploiement-svc.md @@ -9,7 +9,7 @@ gestionnaire de configuration. Après cet échauffement, vous devriez être prêt à créer un *playbook* dédié à l'installation d'un serveur [Matrix](https://matrix.org/) : -![Riot](riot.png "Riot : un des clients utilisable pour joindre son serveur Matrix") +![Element](riot.png "Element : un des clients utilisable pour joindre son serveur Matrix") Vous connaissez et utilisez sans doute Slack, un service de messagerie @@ -34,7 +34,7 @@ dédié lorsque c'est possible (pas de `root` !), droits d'accès et permissions des répertoires, etc. Profitez des [modules de base de -données](http://docs.ansible.com/ansible/latest/list_of_database_modules.html) +données](https://docs.ansible.com/ansible/2.9/modules/list_of_database_modules.html) pour l'initialiser correctement. Et bien entendu de l'ensemble des modules décrits dans la documentation standard ! @@ -58,7 +58,7 @@ de données. Le *dump* obtenu est à placer dans `/var/backups/`. ## Client de test Vous n'êtes pas tenu d'installer un client. Pour vos tests, vous pouvez -utiliser , en changeant l'adresse du serveur Matrix pour +utiliser , en changeant l'adresse du serveur Matrix pour votre sous-domaine dédié à Matrix (normalement ). (Conservez le serveur d'identité à ). @@ -68,7 +68,7 @@ votre sous-domaine dédié à Matrix (normalement ## Validation Pour valider l'installation de votre serveur, rejoignez le canal -`#adlin:nemunai.re` et envoyez un message « Ping ! » à `@nemubot:nemunai.re` -qui s'occupera de valider ce pallier. +`#adlin:nemunai.re` et envoyez un message « Ping ! » pour signaler votre +présence. Vous devriez également pouvoir tester entre-vous. diff --git a/tutorial/ansible/maatma.md b/tutorial/ansible/maatma.md index d9c3832..1b4af3a 100644 --- a/tutorial/ansible/maatma.md +++ b/tutorial/ansible/maatma.md @@ -42,9 +42,10 @@ Tunnel IPv6 Au premier lancement de votre VM, la machine vous demandera d'indiquer un jeton afin de mettre en place le tunnel IPv6. -Afin d'en obtenir un, rendez-vous sur la page Tunnels et créez un nouveau -tunnel. Un jeton de 10 caractères s'affichera alors, c'est celui que vous -devrez recopier dans le terminal (attention à la casse !). +Afin d'en obtenir un, rendez-vous sur la [page +Tunnels](https://adlin.nemunai.re/maatma/tunnels) et créez un nouveau +tunnel. Un jeton de 10 caractères s'affichera alors, c'est celui que +vous devrez recopier dans le terminal (attention à la casse !). ### Test du tunnel diff --git a/tutorial/ansible/nameserver.md b/tutorial/ansible/nameserver.md index 0547423..b8ee6ce 100644 --- a/tutorial/ansible/nameserver.md +++ b/tutorial/ansible/nameserver.md @@ -19,3 +19,136 @@ Voici les grandes étapes : * tester avec `dig @9.9.9.9 ANY login-x.srs.p0m.fr` que votre serveur est bien joignable. Un ouvrage de référence, qui répondra à l'intégralité de vos questions et manières d'utiliser votre serveur DNS se trouve à . + + +## Coller aux spécifications + +Pourquoi une telle complexité apparente pour déléguer une zone DNS ? + +Vous êtes sur le point d'obtenir le contrôle total d'un domaine : +`login-x.srs.p0m.fr.`. Cela signifie que vous allez pouvoir décider, +depuis votre propre serveur de noms (respectueux des +[standards](https://www.ietf.org/rfc/rfc1034.txt)), comment vous allez +répondre aux serveurs résolveurs des utilisateurs. + +Le protocole DNS étant décentralisé, mais basé sur une arborescence +unique, il est nécessaire que les serveurs faisant autorité sur une +zone (`fr.`, `srs.p0m.fr.`, ...) fournissent toutes les informations +nécessaire pour que cette délégation fonctionne. + +À cet instant, vous connaissez l'adresse IPv6 routable sur Internet de votre +serveur de noms (comme vous n'avez aujourd'hui qu'une seule machine, c'est +l'adresse de votre tunnel). Il va donc falloir indiquer à Internet que c'est +cette IP qu'il faut contacter, si l'on veut interroger le serveur faisant +autorité. + +Concrètement, vous allez devoir vous attribuer un sous-domaine pour votre +serveur de noms, car c'est grâce à un enregistrement `NS` figurant dans la zone +parente (pour vous, la zone parente c'est `srs.p0m.fr`) que la délégation va se +faire. + +Dans le cas basique, on va utiliser les serveurs de quelqu'un d'autre (d'un +hébergement spécialisé par exemple, ou celui de son bureau d'enregistrement +s'ils proposent ce service). Auquel cas, on fera figurer ce genre +d'informations : + +``` +42sh$ dig @e.ext.nic.fr. NS epita.fr. +;; ->>HEADER<<- opcode: QUERY; status: NOERROR; id: 17590 +;; Flags: qr rd; QUERY: 1; ANSWER: 0; AUTHORITY: 3; ADDITIONAL: 0 + +;; QUESTION SECTION: +;; epita.fr. IN NS + +;; AUTHORITY SECTION: +epita.fr. 172800 IN NS tooty.ionis-it.com. +epita.fr. 172800 IN NS kazooie.ionis-it.com. +epita.fr. 172800 IN NS banjo.ionis-it.com. + +;; Received 100 B +;; Time 2042-12-04 13:42:23 CET +;; From 2a00:d78:0:102:193:176:144:22@53(UDP) in 13.9 ms +``` + +La commande est envoyée spécifiquement aux serveurs de l'AFNIC, faisant +autorité pour la zone `fr.`. Les serveurs de l'AFNIC nous indiquent en retour, +que `epita.fr.` est déléguée à Ionis, et qu'elle dispose de 3 serveurs faisant +autorité. + +Mais dans votre cas, vous hébergez vous-même votre propre serveur au sein de +votre zone, vous n'avez pas d'autre domaine à votre disposition. C'est +également le cas de Wikipédia : + +``` +42sh$ dig @d0.org.afilias-nst.org. NS wikipedia.org. +;; ->>HEADER<<- opcode: QUERY; status: NOERROR; id: 47396 +;; Flags: qr rd; QUERY: 1; ANSWER: 0; AUTHORITY: 3; ADDITIONAL: 3 + +;; QUESTION SECTION: +;; wikipedia.org. IN NS + +;; AUTHORITY SECTION: +wikipedia.org. 86400 IN NS ns0.wikimedia.org. +wikipedia.org. 86400 IN NS ns2.wikimedia.org. +wikipedia.org. 86400 IN NS ns1.wikimedia.org. + +[...] +``` + +On voit ici que pour résoudre les sous-domaines de `wikipedia.org.`, il faut +demander à `nsX.wikimedia.org.`. Mais comment obtenir alors, l'adresse de ces +serveurs de noms, puisque l'on ne sait pas où les contacter ...! + +C'est là que les *GLUE records* entrent en jeu ! + +J'ai volontairement tronqué la sortie de la commande précédente, en entier, c'est : + +``` +42sh$ dig @d0.org.afilias-nst.org. NS wikipedia.org. +;; ->>HEADER<<- opcode: QUERY; status: NOERROR; id: 47396 +;; Flags: qr rd; QUERY: 1; ANSWER: 0; AUTHORITY: 3; ADDITIONAL: 3 + +;; QUESTION SECTION: +;; wikipedia.org. IN NS + +;; AUTHORITY SECTION: +wikipedia.org. 86400 IN NS ns0.wikimedia.org. +wikipedia.org. 86400 IN NS ns2.wikimedia.org. +wikipedia.org. 86400 IN NS ns1.wikimedia.org. + +;; ADDITIONAL SECTION: +ns0.wikimedia.org. 86400 IN A 208.80.154.238 +ns1.wikimedia.org. 86400 IN A 208.80.153.231 +ns2.wikimedia.org. 86400 IN A 91.198.174.239 + +;; Received 143 B +;; Time 2042-12-04 13:42:23 CET +;; From 2001:500:f::1@53(UDP) in 15.2 ms +``` + +Afin d'éviter de tourner en rond sans jamais avoir réponse à notre question, en +même temps que de nous répondre sur qui sont les serveurs faisant autorité pour +la zone `wikipedia.org.`, les serveurs gérant la zone `org.` indiquent +également à quelle adresse ont peut les contacter. + +Ce sont les administrateurs de la zone `wikipedia.org.` qui ont indiqué à +`org.` quels étaient leurs serveurs de noms. Et ils ont également donné des +*GLUE records*, pour permettre à la magie d'opérer. + +À vous maintenant de créer votre zone, en envoyant sur Maatma, le nom de +domaine votre serveur de noms, ainsi que le *GLUE record* qui lui correspond. + + +## DNSSEC (bonus) + +En bonus, vous devriez sécuriser les réponses envoyées par votre serveur DNS. + +Pour ce faire, et toujours parce que l'on se trouve dans le cadre d'une +structure arborescente, les clefs publiques permettant de valider les +enregistrements d'un domaine en particulier, sont publiées dans la zone +parente. C'est pourquoi, vous disposez sur Maatma, d'une interface vous +permettant d'indiquer vos clefs publiques. + +De nombreux articles sont disponibles sur Internet pour vous permettre de +configurer DNSSEC en fonction du serveur autoritaire que vous aurez choisi. À +vous de jouer ! diff --git a/tutorial/ansible/setup.md b/tutorial/ansible/setup.md index 8fccf49..44dd2bb 100644 --- a/tutorial/ansible/setup.md +++ b/tutorial/ansible/setup.md @@ -33,7 +33,7 @@ L'image d'installation ---------------------- Vous pouvez télécharger l'ISO du TP depuis -. +. Cette image contient un système Debian minimaliste, en partie préinstallé afin de vous permettre de commencer à travailler sans plus attendre ! @@ -261,8 +261,8 @@ machine, afin qu'elle ne teste que le lecteur de CD virtuel. Connexion --------- -La machine ne se connecte pas au réseau toute seule, vous allez devoir l'aider -en reproduisant les étapes que nous avons apprises au TP précédent. +Si la machine ne se connecte pas au réseau toute seule, vous allez devoir +l'aider en reproduisant les étapes que nous avons apprises au TP précédent. ### Requête DHCP @@ -295,7 +295,7 @@ pouvoir émettre un paquet sur le réseau. ``` 42sh$ ip link 1: enp3s0: mtu 1500 qdisc pfifo_fast state DOWN mode DEFAULT group default qlen 1000 - ^^^^^^^^^^ + ^^^^^^^^^^ ```
    diff --git a/tutorial/ansible/tutorial.md b/tutorial/ansible/tutorial.md index d471639..48b0bcb 100644 --- a/tutorial/ansible/tutorial.md +++ b/tutorial/ansible/tutorial.md @@ -3,18 +3,18 @@ title: Administration Linux avancée -- TP n^o^ 2 subtitle: "Maatma : l'hébergeur DIY" author: Pierre-Olivier *nemunaire* [Mercier]{.smallcaps} institute: EPITA -date: Lundi 16 mars 2020 +date: Jeudi 4 mars 2021 abstract: | Durant ce deuxième TP, nous allons apprendre à déployer des services sur un serveur, de manière industrielle ! \vspace{1em} - La partie 4 de ce TP est un projet à rendre à au plus tard - le **lundi 30 mars 2020 à 00 h 42 du matin**. Consultez la dernière + La partie 5 de ce TP est un projet à rendre à au plus tard + le **jeudi 18 mars 2021 à 12 h 42**. Consultez la dernière section de chaque partie pour plus d'information sur les éléments à rendre. Et n'oubliez pas de répondre aux [questions de - cours](https://adlin.nemunai.re/quiz/2). + cours](https://adlin.nemunai.re/quiz/9). En tant que personnes sensibilisées à la sécurité des échanges électroniques, vous devrez m'envoyer vos rendus signés avec votre clef PGP. Pensez à diff --git a/tutorial/ansible/vitrine.md b/tutorial/ansible/vitrine.md index 833cbc3..080fb9a 100644 --- a/tutorial/ansible/vitrine.md +++ b/tutorial/ansible/vitrine.md @@ -20,7 +20,7 @@ reporter dans un fichiers au chapitre suivant ! Ma première vitrine ------------------- -Sur le domaine `login_x.adlin2021.p0m.fr`, déployez une vitrine d'entreprise +Sur le domaine `login-x.adlin2022.p0m.fr`, déployez une vitrine d'entreprise basique (pas besoin d'un Wordpress, un simple lot de pages HTML fera l'affaire). Vous aurez pour cela besoin d'un serveur web, dont le choix est laissé à votre @@ -29,11 +29,13 @@ discrétion. Vous pouvez utiliser les services de [Let's Encrypt](https://letsencrypt.org/) pour obtenir un certificat TLS. Compte tenu des limitations imposées, vous ne pourrez pas tous en créer un aujourd'hui, mais n'hésitez pas à retenter un peu -plus tard dans la semaine. +plus tard dans la semaine. Vous pouvez également obtenir vos certificats depuis +de nombreux autres services gratuits similaire : +[ZeroSSL](https://zerossl.com/), [buypass](https://www.buypass.com/), ... -*D'ailleurs, si vous disposez de votre propre nom de domaine et que vous -souhaitez l'utiliser pour ce TP, n'hésitez pas à me solliciter pour que je -mette en place les redirections adéquates.* +D'ailleurs, si vous disposez de votre propre nom de domaine et que vous +souhaitez l'utiliser pour ce TP, vous pouvez suivre les instructions dans +l'interface de Maatma pour pouvoir l'utiliser. Une fois votre serveur web configuré et votre vitrine installée, accédez à diff --git a/tutorial/ansible/what.md b/tutorial/ansible/what.md index 58ee80a..18dd802 100644 --- a/tutorial/ansible/what.md +++ b/tutorial/ansible/what.md @@ -7,7 +7,7 @@ Accéder à la machine virtuelle ------------------------------ Une fois la machine virtuelle démarrée, vous pouvez vous y connecter en `root` -avec le mot de passe `adlin2021`. +avec le mot de passe `adlin2022`. Vous pouvez également démarrer en mode *single user*, mais comme votre disque n'est sans doute pas encore utilisable à ce stade, vous ne pourrez pas changer @@ -30,6 +30,13 @@ local (sans passer par un NAT), n'oubliez pas de modifier le mot de passe `root` pour éviter que n'importe qui sur le réseau local (et ayant accès à ce TP), ne rentre sur votre machine. +Afin de vous faciliter la configuration de la machine par la suite, vos clefs +SSH publiques, [déclarées au +CRI](https://cri.epita.fr/users/nemunaire/ssh-keys/), sont automatiquement +ajoutées à l'utilisateur `root`. Rendez-vous dans l'interface du CRI pour les +mettre à jour si besoin. Notez qu'elles ne sont retéléchargées que si le +fichier `authorized_keys` n'existe pas. + ### Création d'un utilisateur