Compare commits

...

23 commits

Author SHA1 Message Date
caaa30c747 tuto2: fix dead URL 2021-03-08 13:44:42 +01:00
405f3d6395 checker: improve matrix check 2021-03-08 13:44:23 +01:00
78c42e312c checker: add test for Matrix 2021-03-07 17:09:11 +01:00
07e91271f4 checker: better handle DNS checks 2021-03-07 12:42:05 +01:00
456694c330 maatma: add mistake indications on home page 2021-03-07 12:41:47 +01:00
6d8f38d749 Use pointer instead of struct 2021-03-07 12:41:47 +01:00
853477e54a checker: deep redesign 2021-03-07 12:41:47 +01:00
4a27c9367e checker: default NS as const 2021-03-07 12:38:37 +01:00
059e3cabec checker: fix problematic file descriptor leak 2021-03-07 12:38:37 +01:00
2bd6b3f18b maatma: add help on pages 2021-03-07 12:38:37 +01:00
8712e33c5a maatma: publish progression on frontpage 2021-03-07 12:38:37 +01:00
f47eb68505 dashboard: extract progress and visual changes 2021-03-07 12:38:37 +01:00
764a790b11 Update tutorial2 2021-03-07 12:38:37 +01:00
5a4650f70e token-validator: update to handle custom domains 2021-03-07 12:38:37 +01:00
efab34d551 checker: don't validate http challenge if error status returns 2021-03-07 12:38:37 +01:00
9cd237daff checker: for each tun IP 2021-03-07 12:38:37 +01:00
0af7437693 libadlin: avoid # char, anchor... 2021-03-07 12:38:37 +01:00
b6eb652929 token-validator: display custom IP 2021-03-07 12:38:36 +01:00
8427a0adb8 token-validator: display user associated domain and delegation 2021-03-07 12:38:36 +01:00
d28b14fa50 token-validator: better calculate IP contained 2021-03-03 18:44:26 +01:00
6fcdc44952 Add missing deps for tuto2 2021-03-03 18:43:11 +01:00
54e1505db9 Update tuto2 for 2022 2021-03-03 18:42:41 +01:00
da1920673d token-validator: use SuffixIP, can modify it and can delete tunnels 2021-03-02 19:08:42 +01:00
37 changed files with 1280 additions and 364 deletions

View file

@ -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

View file

@ -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
})
}
}
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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 {

View file

@ -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 {

View file

@ -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 {

View file

@ -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
}

View file

@ -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)
}

View file

@ -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
}

View file

@ -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, &gt, chid); err != nil {
if err := challenges[chid-1].Check(std, &gt, chid); err != nil {
log.Printf("%s just try ch#%d: %s\n", std.Login, chid, err)
return nil, err
}

View file

@ -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")
}

View file

@ -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 {

View file

@ -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 {

View file

@ -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 @@
<ul ng-if="ips" style="padding-left:0">
<li><strong>Domaine&nbsp;:</strong> <a href="http://{{ips.adn}}/">{{ips.adn}}</a></li>
<li><strong>Délégation&nbsp;:</strong> <a href="http://{{ips.ddn}}/">{{ips.ddn}}</a></li>
<li><strong>IPv6&nbsp;:</strong> {{ips.wg}}1/96</li>
<li><strong>IPv6&nbsp;:</strong> {{ips.wg}}/80</li>
</ul>
<div ng-repeat="(tutoid,tuto) in tuto_progress">
<hr>
<h6>TP {{tutoid+1}}</h6>
<span class="badge mr-1" ng-repeat="(ch,t) in tuto_progress[tutoid]" ng-class="{'badge-warning': mychallenges[ch] && mychallenges[ch].recent && (tutoid != 0 && mychallenges[ch].recent > 300), 'badge-info': mychallenges[ch] && mychallenges[ch].recent && mychallenges[ch].recent <= 300 && mychallenges[ch].recent >= 120, 'badge-success': mychallenges[ch] && mychallenges[ch].recent && (tutoid == 0 || mychallenges[ch].recent < 120), 'badge-danger': !mychallenges[ch]}" title="{{ t.title }} @ {{ mychallenges[ch].time | date: 'medium' }} {{ mychallenges[ch].recent }}" ng-bind="t.label"></span>
<span class="badge mr-1" ng-repeat="(ch,t) in tuto_progress[tutoid]" ng-class="{'badge-warning': mychallenges[ch] && mychallenges[ch].recent && (tutoid != 0 && mychallenges[ch].recent > 300), 'badge-info': mychallenges[ch] && mychallenges[ch].recent && mychallenges[ch].recent <= 300 && mychallenges[ch].recent >= 120, 'badge-success': mychallenges[ch] && mychallenges[ch].recent && (tutoid == 0 || mychallenges[ch].recent < 120), 'badge-danger': !mychallenges[ch] || !mychallenges[ch].recent}" title="{{ t.title }} @ {{ mychallenges[ch].time | date: 'medium' }} {{ mychallenges[ch].recent }}" ng-bind="t.label"></span>
</div>
</p>
</div>
@ -108,7 +108,7 @@
</div>
</div>
<div class="card student d-flex flex-column justify-content-between" ng-repeat="(login, mychallenges) in students" style="background-image: url('https://photos.cri.epita.fr/square/{{ mychallenges.img | lowercase }}')">
<div class="card student d-flex flex-column justify-content-between" ng-repeat="(login, mychallenges) in students" ng-if="login != 'nemunaire'" style="background-image: url('https://photos.cri.epita.fr/square/{{ mychallenges.img | lowercase }}')">
<h5 class="login text-truncate" title="{{ login }}">
<span class="badge" ng-class="{'badge-success': mychallenges['ping'] && mychallenges['ping'].recent < 120, 'badge-info': mychallenges['ping'] && mychallenges['ping'].recent >= 120 && mychallenges['ping'].recent < 300, 'badge-warning': mychallenges['ping'] && mychallenges['ping'].recent >= 300 && mychallenges['ping'].recent < 900, 'badge-danger': mychallenges['ping'] && mychallenges['ping'].recent >= 900, 'badge-dark': !mychallenges['ping']}" title="{{ mychallenges['ping'].time }}">
&#x1f4bb;
@ -116,7 +116,7 @@
<a class="text-dark" href="/dashboard/{{login}}">{{ login }}</a>
</h5>
<div class="d-flex flex-wrap justify-content-around" style="padding: 0">
<span class="badge" style="margin-left: .07rem" ng-repeat="(ch,t) in tuto_progress[tutoid]" ng-class="{'badge-warning': mychallenges[ch] && mychallenges[ch].recent && (tutoid != 0 && mychallenges[ch].recent > 300), 'badge-info': mychallenges[ch] && mychallenges[ch].recent && mychallenges[ch].recent <= 300 && mychallenges[ch].recent >= 120, 'badge-success': mychallenges[ch] && mychallenges[ch].recent && (tutoid == 0 || mychallenges[ch].recent < 120), 'badge-danger': !mychallenges[ch]}" title="{{ t.title }} @ {{ mychallenges[ch].time | date: 'medium' }} {{ mychallenges[ch].recent }}" ng-bind="t.label"></span>
<span class="badge badge-sm" style="margin-left: .07rem" ng-repeat="(ch,t) in tuto_progress[tutoid]" ng-class="{'badge-warning': mychallenges[ch] && mychallenges[ch].recent && (tutoid != 0 && mychallenges[ch].recent > 300), 'badge-info': mychallenges[ch] && mychallenges[ch].recent && mychallenges[ch].recent <= 300 && mychallenges[ch].recent >= 120, 'badge-success': mychallenges[ch] && mychallenges[ch].recent && (tutoid == 0 || mychallenges[ch].recent < 120), 'badge-danger': !mychallenges[ch] || !mychallenges[ch].recent}" title="{{ t.title }} @ {{ mychallenges[ch].time | date: 'medium' }} {{ mychallenges[ch].recent }}" ng-bind="t.icon"></span>
</div>
</div>
</div>
@ -126,6 +126,7 @@
<script src="js/angular-resource.min.js"></script>
<script src="js/angular-route.min.js"></script>
<script src="js/angular-sanitize.min.js"></script>
<script src="js/adlin-common.js"></script>
<script src="js/adlin-dashboard.js"></script>
</body>
</html>

View file

@ -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"},
},
];

View file

@ -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); });
})

View file

@ -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,

View file

@ -46,6 +46,7 @@
<script src="js/angular-resource.min.js"></script>
<script src="js/angular-route.min.js"></script>
<script src="js/angular-sanitize.min.js"></script>
<script src="js/adlin-common.js"></script>
<script src="js/adlin-main.js"></script>
</body>
</html>

View file

@ -2,7 +2,10 @@
Noms de domaine
</h2>
<h3>Association simple</h3>
<h3>
Association simple
<span class="badge badge-pill badge-dark" title="Une association simple est un sous-domaine qui va vous être attribué, sans que vous ayez à gérer de serveur DNS. Ce domaine sera associé à une IP que vous pourrez changer par la suite. C'est la première étape, plus facile que la délégation.">?</span>
</h3>
<table class="table table-striped table-hover">
<thead>
@ -28,6 +31,9 @@
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true" ng-show="pleaseWaitNewAssociation"></span>
Demander une nouvelle association
</button>
<button class="btn ml-2" ng-class="{'btn-secondary': !adomains || !adomains.length, 'btn-success': adomains && adomains.length}" ng-disabled="!adomains || !adomains.length" ng-click="useMyAssociationD()">
Utiliser mon domaine
</button>
</td>
</tr>
</tfoot>
@ -35,7 +41,10 @@
<hr class="my-4">
<h3>Délégation</h3>
<h3>
Délégation
<span class="badge badge-pill badge-dark" title="Une délégation va vous permettre de gérer vous-même votre domaine sur Internet. Nous vous offrons login.srs.p0m.fr, mais vous pouvez aussi choisir de créer la délégation sur votre domaine, si vous en possédez un. Rendez-vous ensuite sur votre serveur pour y installer un serveur de noms de domaine autoritaire tel que nsd, bind, knot ou encore powerDNS...">?</span>
</h3>
<ul class="nav nav-tabs" id="ddomainTabs" role="tablist">
<li class="nav-item">
@ -48,11 +57,17 @@
<a class="nav-link" data-toggle="tab" data-target="#DNSSEC" role="tab" aria-controls="dnssec" aria-selected="false">DNSSEC</a>
</li>
</ul>
<div class="tab-content" id="myTabContent">
<div ng-if="!student.delegated_domain" class="tab-content" id="myTabContent">
<div class="tab-pane fade show active" id="NS" role="tabpanel" aria-labelledby="ns-tab">
<div ng-repeat="domain in ddomains">
<h4 class="text-muted">{{ domain }}</h4>
<h4 class="text-muted">
{{ domain }}
<button class="btn btn-sm btn-info ml-2" ng-if="$first" ng-click="useMyDelegationD()">
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true" ng-show="pleaseWaitNewDelegation"></span>
Utiliser mon domaine
</button>
</h4>
<table class="table table-striped table-hover" ng-controller="NSDomainsController">
<thead>
@ -65,7 +80,7 @@
<tr ng-repeat="rr in domainNS">
<td><span ng-repeat="val in rr.values">{{ val }} </span></td>
<td>
<span class="badge badge-pill badge-secondary">Not implemented yet</span>
<!--span class="badge badge-pill badge-secondary">Not implemented yet</span-->
</td>
<td>
<button class="btn btn-warning" ng-click="updateNS(domain, rr)">Modifier</button>
@ -180,7 +195,31 @@
</div>
</div>
<div ng-if="student.delegated_domain">
<h4 class="text-muted">
{{ student.delegated_domain }}
<button class="btn btn-sm btn-danger ml-2" ng-if="student.delegated_domain" ng-click="delMyDomainDelegatedD()">
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true" ng-show="pleaseWaitNewDelegation"></span>
Ne plus utiliser mon domaine
</button>
</h4>
<p>
Vous avez choisi d'utiliser votre propre domaine pour réaliser la délégation.
</p>
<p>
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.
</p>
<p>
Pour rappel, voici les enregistrements à rajouter&nbsp;:
</p>
<pre>
;; 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]
</pre>
</div>
<div class="modal" id="AssocModal" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
@ -224,6 +263,85 @@
</div>
</div>
<div class="modal" id="AssocMyDomainModal" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Utiliser mon domaine</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<p>
Si tu possèdes ton propre domaine, tu peux l'utiliser à la place du domaine qui t'es proposé ici&nbsp;!
</p>
<p>
Pour cela, rien de plus simple. Choisis un sous-domaine de ton choix, dans ton domaine (par exemple <code>adlin.nemunai.re</code>)&nbsp;:
</p>
<form class="ml-2 mr-2">
<div class="form-group">
<label class="form-label" for="mysubdomain">Ton sous-domaine&nbsp;:</label>
<input class="form-control" id="mysubdomain" ng-model="assoc.cname" autofocus>
</div>
</form>
<p>
Ensuite, dans ta zone DNS, ajoute un alias pointant vers le domaine sous <code>adlin20xx.p0m.fr</code> qui t'es proposé&nbsp;:
</p>
<pre>
{{ assoc.cname }} 300 IN CNAME {{ assoc.domain }}
</pre>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Annuler</button>
<button type="button" class="btn btn-primary" ng-disabled="!assoc.cname" ng-click="newMyDomainAssociationD(assoc)">Ok, c'est fait</button>
<button type="button" class="btn btn-danger" ng-show="student.associated_domain" ng-click="delMyDomainAssociationD(assoc)">Supprimer l'association enregistrée</button>
</div>
</div>
</div>
</div>
<div class="modal" id="DelegateMyDomainModal" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Utiliser mon domaine comme délégation</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<p>
Tu peux utiliser ton propre domaine à la place du domaine qui t'es proposé ici&nbsp;!
</p>
<p>
Commence par choisir un sous-domaine de ton choix (différent de l'association ci-dessus), dans ton domaine (par exemple <code>adlin.nemunai.re</code>)&nbsp;:
</p>
<form class="ml-2 mr-2">
<div class="form-group">
<label class="form-label" for="mysubdomaind">Ton sous-domaine&nbsp;:</label>
<input class="form-control" id="mysubdomaind" ng-model="assoc.ns" autofocus>
</div>
</form>
<p>
Ensuite, configure ta zone DNS, pour réaliser la délégation. L'interface
</p>
<pre>
;; Delegation {{ assoc.ns }} to the given name server
{{ assoc.ns }} 300 IN NS ns.{{ assoc.ns }}
;; GLUE record to serve along with the previous record
ns.{{ assoc.ns }} 300 IN AAAA [your NS ip]
</pre>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Annuler</button>
<button type="button" class="btn btn-primary" ng-disabled="!assoc.ns" ng-click="newMyDomainDelegationD(assoc)">Ok, c'est fait</button>
</div>
</div>
</div>
</div>
<div class="modal" id="NSModal" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">

View file

@ -10,26 +10,31 @@
Connectez-vous&nbsp;!
</h2>
<h2 ng-if="isLogged">
<h2 ng-if="isLogged" class="mb-4">
Qu'allons-nous faire aujourd'hui&nbsp;? <a href="/dashboard/{{ isLogged.login }}" class="text-muted">Voir l'avancement...</a>
</h2>
<div ng-controller="ProgressionController" class="row" ng-if="isLogged">
<div class="col">
<strong>TP 2&nbsp;</strong>
<span class="badge" ng-class="{'badge-success': mychallenges[100], 'badge-danger': !mychallenges[100]}">HTTP</span>
<span class="badge" ng-class="{'badge-success': mychallenges[101], 'badge-danger': !mychallenges[101]}">HTTPS</span>
<span class="badge" ng-class="{'badge-success': mychallenges[102], 'badge-danger': !mychallenges[102]}">DNS</span>
<span class="badge" ng-class="{'badge-success': mychallenges[103], 'badge-danger': !mychallenges[103]}">Matrix</span>
</div>
<div class="col">
<strong>TP 3&nbsp;</strong>
<span class="badge" ng-class="{'badge-success': mychallenges[200], 'badge-danger': !mychallenges[200]}">HTTP</span>
<span class="badge" ng-class="{'badge-success': mychallenges[201], 'badge-danger': !mychallenges[201]}">HTTPS</span>
<span class="badge" ng-class="{'badge-success': mychallenges[202], 'badge-danger': !mychallenges[202]}">DNS</span>
<span class="badge" ng-class="{'badge-success': mychallenges[203], 'badge-danger': !mychallenges[203]}">Matrix</span>
<div ng-controller="ProgressionController" ng-if="isLogged">
<div ng-repeat="(tutoid,tuto) in tuto_progress" class="mb-2 row">
<strong class="col-auto mr-2">TP {{tutoid+1}}</strong>
<div class="col">
<div class="ml-1 align-items-center" ng-class="{'d-inline-block': !mychallenges[ch] || !mychallenges[ch].error || mychallenges[ch].error == 'OK', 'd-flex': !(!mychallenges[ch] || !mychallenges[ch].error || mychallenges[ch].error == 'OK')}" ng-repeat="(ch,t) in tuto_progress[tutoid]">
<span class="badge mr-1" ng-class="{'badge-success': mychallenges[ch] && (!mychallenges[ch].error || mychallenges[ch].error == 'OK'), 'badge-warning': mychallenges[ch] && mychallenges[ch].error && mychallenges[ch].error != 'OK', 'badge-danger': !mychallenges[ch] || !mychallenges[ch].time}" title="{{ t.title }} @ {{ mychallenges[ch].time | date: 'medium' }} {{ mychallenges[ch].recent }}" ng-bind="t.label"></span>
<pre class="ml-1 mb-0" ng-show="mychallenges[ch] && mychallenges[ch].error && mychallenges[ch].error != 'OK'">{{ mychallenges[ch].error }}</pre>
</div>
</div>
</div>
</div>
<h2 class="mt-4 mb-2" ng-if="isLogged">
Besoin d'aide&nbsp;?
</h2>
<p ng-if="isLogged">
Rejoignez-nous sur notre canal
<a href="https://matrix.to/#/#maatma:nemunai.re" target="_blank"><img src="https://matrix.to/images/matrix-logo-3303998978.svg" alt="Rejoingnez-nous sur notre canal Matrix"></a>
</p>
</div>
<div class="card-deck">

View file

@ -6,22 +6,47 @@
<thead>
<tr>
<th></th>
<th>Token</th>
<th>Dernière utilisation</th>
<th>Clef publique</th>
<th>
Token
<span class="badge badge-pill badge-dark" title="Utilisez l'un de vos tokens pour connecter votre machine virtuelle à un réseau IPv6 routable sur Internet. Ce token vous sera demandé au premier démarrage de la VM.">?</span>
</th>
<th>
Suffix
<span class="badge badge-pill badge-dark" title="Si vous souhaitez pouvoir utiliser plusieurs tunnels en parallèle, tout en bénéficiant de la mise en place automatique du tunnel au démarrage, vous allez devoir vous définir un suffix. Il s'agit de la dernière partie de l'adresse qui sera attribué à votre machine.">?</span>
</th>
<th>
Dernière utilisation
<span class="badge badge-pill badge-dark" title="Si vous constatez une utilisation frauduleuse, révoquez votre tunnel.">?</span>
</th>
<th>
Clef publique
<span class="badge badge-pill badge-dark" title="La clef publique est automatiquement envoyée par votre machine virtuelle lorsqu'elle valide le token. C'est une clef cryptographique qui est utiliser pour chiffrer le canal VPN utilisé.">?</span>
</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="tunnel in tunnels" ng-class="{'bg-success': tunnel.dump}">
<td>
<span ng-if="tunnel.Dump" class="text-bold text-success">&gt;</span>
<span ng-if="!tunnel.Dump">&#x274c;</span>
<span ng-if="tunnel.Dump" title="Tunnel actif (voir information ci-dessous)" class="font-weight-bold text-success">&gt;</span>
<span ng-if="!tunnel.Dump" title="Tunnel inactif">&#x274c;</span>
</td>
<td><code>{{ tunnel.TokenText }}</code></td>
<td ng-if="tunnel.edit">
<input class="form-control" ng-model="tunnel.newData.SuffixIP" placeholder="Suffixe d'IP par défaut" autofocus integer>
</td>
<td ng-if="!tunnel.edit && tunnel.SuffixIP">{{ tunnel.SuffixIP }}</td>
<td ng-if="!tunnel.edit && !tunnel.SuffixIP">Par défaut</td>
<td>{{ tunnel.Time | date:"medium" }}<span ng-if="tunnel.Version"> (VM TP {{ tunnel.Version }})</span></td>
<td><code ng-show="tunnel.PubKey">{{ tunnel.PubKey }}</code><span ng-show="!tunnel.PubKey">(none)</span></td>
<td><code class="text-truncate" title="{{ tunnel.PubKey }}" ng-show="tunnel.PubKey">{{ tunnel.PubKey }}</code><span ng-show="!tunnel.PubKey">(none)</span></td>
<td>
<button class="btn btn-sm btn-danger" ng-click="dropTunnel(tunnel)" disabled>
<button class="btn btn-sm btn-info" ng-click="editTunnel(tunnel)" ng-if="!tunnel.edit">
Éditer
</button>
<button class="btn btn-sm btn-success" ng-click="updateTunnel(tunnel)" ng-if="tunnel.edit">
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true" ng-show="tunnel.pleaseWaitUpdate"></span>
Enregistrer
</button>
<button class="btn btn-sm btn-danger" ng-click="dropTunnel(tunnel)">
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true" ng-show="tunnel.pleaseWaitDrop"></span>
Révoquer
</button>
@ -42,7 +67,10 @@
<div class="card-deck mb-4">
<div class="card">
<h4 class="card-header">Paramètres du tunnel</h4>
<h4 class="card-header">
Paramètres du tunnel
<span class="float-right badge badge-pill badge-dark" title="Ce cadre vous montre les informations sur le routeur hébergeant le tunnel chez Maatma. Vérifiez notamment que vous avez les mêmes paramètres dans vos machines.">?</span>
</h4>
<ul class="list-group list-group-flush">
<li class="list-group-item"><strong>Statut&nbsp;:</strong> {{ wginfo.status }}</li>
<li class="list-group-item"><strong>Clef publique du serveur&nbsp;:</strong> <code>{{ wginfo.srv_pubkey }}</code></li>
@ -51,10 +79,11 @@
<li class="list-group-item"><strong>Gateway/passerelle IPv6&nbsp;:</strong> {{ wginfo.srv_gw6 }}</li>
</ul>
</div>
<div class="card" ng-repeat="tunnel in tunnels" ng-if="tunnel.Dump">
<div class="card mb-2" ng-repeat="tunnel in tunnels" ng-if="tunnel.Dump">
<h4 class="card-header">
État de mon tunnel
<span class="badge" ng-class="{'badge-success': PING && PING < 90, 'badge-danger': !PING || PING >= 90}" title="{{ PING_time }}">&#x1f4bb;</span>
<span class="badge" ng-if="$first" ng-class="{'badge-success': PING && PING < 90, 'badge-danger': !PING || PING >= 90}" title="{{ PING_time }}">&#x1f4bb;</span>
<span class="float-right badge badge-pill badge-dark" title="Voici les informations concernant l'un de vos tunnels actuellement actif (pas forcément connecté, reportez-vous à l'icône pour savoir cela).">?</span>
</h4>
<ul class="list-group list-group-flush">
<li class="list-group-item"><strong>Clef publique pair&nbsp;:</strong> <code>{{ tunnel.Dump.PubKey }}</code></li>

View file

@ -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()

View file

@ -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, &gt); err != nil {
return nil, err

View file

@ -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 {

View file

@ -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
}

View file

@ -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 {

View file

@ -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:::

View file

@ -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 <https://login-x.adlin2021.p0m.fr/>.
accessible depuis votre domaine <https://login-x.adlin2022.p0m.fr/>.
Mon première commande
@ -85,7 +87,7 @@ Lancez ensuite la commande suivante :
<div lang="en-US">
```
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 :
- <https://ringyt.wordpress.com/2017/05/23/default-hardened-ssh-server-config/> ;
- <https://linuxhandbook.com/ssh-hardening-tips/> ;
- <https://stribika.github.io/2015/01/04/secure-secure-shell.html>.
Mettez en place un *handler* pour relancer votre serveur SSH en cas de

View file

@ -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 <https://riot.im/app/>, en changeant l'adresse du serveur Matrix pour
utiliser <https://app.element.io/>, en changeant l'adresse du serveur Matrix pour
votre sous-domaine dédié à Matrix (normalement
<https://matrix.login-x.srs.p0m.fr/>). (Conservez le serveur d'identité à
<https://vector.im>).
@ -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.

View file

@ -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

View file

@ -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 à <http://www.zytrax.com/books/dns/>.
## 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 !

View file

@ -33,7 +33,7 @@ L'image d'installation
----------------------
Vous pouvez télécharger l'ISO du TP depuis
<https://adlin.nemunai.re/ressources/tuto2.iso>.
<https://adlin.nemunai.re/resources/tuto2.iso>.
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: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc pfifo_fast state DOWN mode DEFAULT group default qlen 1000
^^^^^^^^^^
^^^^^^^^^^
```
</div>

View file

@ -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 à <adlin@nemunai.re> 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 à <adlin@nemunai.re> 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 à

View file

@ -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 à

View file

@ -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