მონაცემთა ბაზებს იშვიათად ტეხავენ. ხშირად უბრალოდ შიგნით შედიან. ინციდენტების დიდი ნაწილი ოთხ რამეზე დადის: პორტს მთელი ინტერნეტი ხედავდა, პაროლი გამოსაცნობი ან განმეორებადი იყო, ერთი ყველაფრის შემძლე ანგარიში სისტემის ყველა საქმეს აკეთებდა, და როცა რაღაც არასწორად წავიდა, აღსადგენი backup არ არსებობდა. უსაფრთხოების ლიტერატურაში ყველა ჭკვიანური რამ ამ ოთხის უკან დგას, და თუ მხოლოდ ესენს გამოასწორებ, პროდაქშენ სისტემათა უმეტესობაზე წინ იქნები.
ეს არის ჩეკლისტი, რომელსაც ერთ შუადღეში გაივლი, იმ თანმიმდევრობით, რომელიც ყველაზე მეტ რისკს პირველად აქრობს. მაგალითებისთვის PostgreSQL და MongoDB გამოვიყენე, რადგან ეს ის ორი ძრავაა, რომელსაც ხალხი ყველაზე ხშირად თვითონ უშვებს, მაგრამ ფორმა ნებისმიერი ბაზისთვის იგივეა: გადაწყვიტე, ვის შეუძლია მასთან მიღწევა, ვის შეუძლია შესვლა, რას აკეთებს თითოეული ანგარიში, დაშიფრე გზა, შეზღუდე ზიანი, რომელიც ერთ კლიენტს შეუძლია მიაყენოს, და შეინახე ასლი, რომლის აღდგენაც დამტკიცებული გაქვს.
ერთი ჩარჩო, რომელიც გზაში გაითვალისწინე. უსაფრთხოება ბოლოს დამატებული თვისება არ არის - ეს გადაწყვეტილებების ერთობლიობაა იმაზე, თუ რამდენად შორს მიდის ზიანი. ყველა შეცდომას ვერ აიცილებ. თითოეული ნაბიჯი პასუხობს კითხვას: როცა რამე არასწორად წავა, რამდენად შორს მივა.
წვდომა ქსელიდან: პირველი და უდიდესი მოგება#
ყველა ბაზა, რომელიც საჯარო მისამართზე უსმენს, აუცილებლად იპოვება. არა "შეიძლება იპოვონ" - აუცილებლად. მასობრივი სკანერები განუწყვეტლივ ავლებენ მთელ მისამართთა სივრცეს 5432, 27017, 3306 და 6379 პორტებზე, და ახალ ჰოსტს ღია პორტით პირველი შესვლის მცდელობა ჩვეულებრივ ჩართვიდან რამდენიმე წუთში ხვდება. 2017 წლის MongoDB-ს გამოძალვის ტალღა ჭკვიანური ექსპლოიტი არ ყოფილა - ეს იყო ათასობით ინსტანცია, 0.0.0.0-ზე მიბმული და გამორთული ავტორიზაციით.
დაიწყე იმის გარკვევით, რა უსმენს სინამდვილეში:
$ ss -ltnpState Recv-Q Send-Q Local Address:Port ProcessLISTEN 0 244 127.0.0.1:5432 users:(("postgres",pid=812,fd=6))LISTEN 0 4096 0.0.0.0:27017 users:(("mongod",pid=944,fd=11))127.0.0.1:5432 მხოლოდ თვით მანქანიდან ჩანს. 0.0.0.0:27017 ჩანს ყველგან, სადაც firewall-ი უშვებს. შემდეგ შეამოწმე გარედან, რადგან იმას, რასაც მანქანა თვლის, და იმას, რასაც ქსელი აკეთებს, სხვადასხვა პასუხი აქვს:
$ nc -vz db.example.net 5432Connection to db.example.net 5432 port [tcp/postgresql] succeeded!პარამეტრები, რომლებიც ამას აკონტროლებს:
| ძრავა | პარამეტრი | უსაფრთხო ნაგულისხმევი |
|---|---|---|
| PostgreSQL | listen_addresses ფაილში postgresql.conf | localhost, ან ერთი კერძო მისამართი |
| PostgreSQL | pg_hba.conf-ის host წესები | კონკრეტული მისამართები, არასოდეს 0.0.0.0/0 |
| MongoDB | net.bindIp ფაილში mongod.conf | 127.0.0.1 და კონკრეტული მისამართები |
| ნებისმიერი | Firewall | შემომავალი ყველაფერი აკრძალულია, დასახელებული წყაროები დაშვებულია |
თუ აპლიკაცია და ბაზა ერთ მანქანაზეა, მიაბი localhost-ს და ამ თავის კითხვა შეწყვიტე. თუ არა, დაუშვი ზუსტად ის მისამართები, რომლებსაც ეს სჭირდება, და სხვა არაფერი. Firewall-ის წესები, რომლებსაც მნიშვნელობა აქვს გასწავლის ამ წესების დაწერას საკუთარი თავის გარეთ დარჩენის გარეშე, ხოლო PostgreSQL-ის დისტანციური კავშირები გვიჩვენებს სამ პარამეტრს, რომლებიც უნდა დაემთხვეს, სანამ დისტანციური კლიენტი საერთოდ დაკავშირდება.
ავტენტიფიკაცია, რომელიც გამოსაცნობი არ არის#
როცა პორტს მხოლოდ ისინი აღწევენ, ვისაც სჭირდებათ, შემდეგი კითხვაა, ვის შეუძლია შესვლა.
PostgreSQL ამას წყვეტს pg_hba.conf-ით, რომელიც იკითხება ზემოდან ქვემოთ და პირველივე შესაბამისი ხაზი იმარჯვებს. სწორედ ეს თანმიმდევრობა ხვდება ხალხს ხაფანგში: ნებადამრთველი ხაზი შემზღუდველის ზემოთ ნიშნავს, რომ შემზღუდველი არასოდეს სრულდება.
# TYPE DATABASE USER ADDRESS METHODlocal all postgres peerhostssl app app_rw 10.0.0.0/24 scram-sha-256hostssl app app_ro 10.0.0.0/24 scram-sha-256host all all 0.0.0.0/0 rejectგამოიყენე scram-sha-256 და არა md5, და არასოდეს trust, რაც პაროლის სრულ არარსებობას ნიშნავს. მომხმარებლების შექმნამდე დააყენე password_encryption = scram-sha-256, თორემ მათი პაროლები ძველი პარამეტრის მიხედვით შეინახება. hostssl host-ის ნაცვლად უარყოფს კავშირს, თუ ის დაშიფრული არ არის, რაც უფრო ძლიერია, ვიდრე კლიენტისთვის თავაზიანად თხოვნა.
MongoDB წვდომის კონტროლის გარეშე მოდის და ეს mongod.conf-ის ყველაზე მნიშვნელოვანი ხაზია:
security: authorization: enablednet: bindIp: 127.0.0.1,10.0.0.5 port: 27017მის გარეშე ადმინისტრატორია ყველა, ვინც პორტს მიაღწევს. მასთან MongoDB იყენებს SCRAM-SHA-256-ს და ყოველ ოპერაციას მომხმარებელი სჭირდება.
თავად პაროლებისთვის: გენერირებული, გრძელი, თითოეულ ბაზაზე უნიკალური, password manager-ში შენახული და staging-სა და production-ს შორის არასოდეს გამეორებული. გენერატორიდან აღებული ოცი შემთხვევითი სიმბოლო სჯობს ჩანაცვლებებიანი დასამახსოვრებელ ფრაზას, რადგან თავდამსხმელის ლექსიკონი ამ ჩანაცვლებებს უკვე შეიცავს. თუ პაროლი ოდესმე ჩატში, ticket-ში ან screenshot-ში ჩასვი, ის დამწვარია - შეცვალე.
ერთი მომხმარებელი - ერთი საქმე#
ეს ნაბიჯი აქცევს გატეხვას ინციდენტად და არა კატასტროფად, და სწორედ ის გამოტოვება ყველაზე ხშირად, რადგან ერთი superuser კავშირის სტრიქონში ყოველთვის მუშაობს.
აპლიკაციას runtime-ზე ცხრილების შექმნა არ სჭირდება. ანალიტიკის დაფას სტრიქონების წაშლა არ სჭირდება. მიგრაციის ხელსაწყოს სქემის უფლებები თვეში ოთხმოცდაათი წამით სჭირდება და არა მუდმივად. ოთხი როლი სისტემათა უმეტესობას ფარავს:
| როლი | უფლებები | ვინ იყენებს |
|---|---|---|
| Owner ან superuser | ყველაფერი | არავინ ყოველდღიურად. ინახება საგანგებო შემთხვევისთვის |
| მიგრაციის მომხმარებელი | სქემის ცვლილებები ერთ ბაზაზე | მხოლოდ deploy pipeline |
| აპლიკაციის მომხმარებელი | ცხრილებზე წაკითხვა და ჩაწერა, DDL-ის გარეშე | გაშვებული აპლიკაცია |
| მხოლოდ წაკითხვის მომხმარებელი | მხოლოდ SELECT | დაფები, ანგარიშგება, ადამიანები, რომლებიც უბრალოდ იხედებიან |
PostgreSQL-ში ეს ასე გამოიყურება:
CREATE ROLE app_rw LOGIN PASSWORD 'generated-here';CREATE ROLE app_ro LOGIN PASSWORD 'a-different-one';GRANT CONNECT ON DATABASE app TO app_rw, app_ro;GRANT USAGE ON SCHEMA public TO app_rw, app_ro;GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO app_rw;GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO app_rw;GRANT SELECT ON ALL TABLES IN SCHEMA public TO app_ro;ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO app_rw;ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO app_ro;ALTER DEFAULT PRIVILEGES ხაზები ის არის, რასაც ხალხი ივიწყებს. მათ გარეშე შემდეგი მიგრაციით შექმნილი ყველა ცხრილი აპლიკაციის მომხმარებლისთვის უხილავია და ღამის სამ საათზე production-ში permission შეცდომას იღებ. PostgreSQL-ის როლები და უფლებები მთელ მოდელს გადის, იმის ჩათვლით, თუ რატომ აქვს PUBLIC-ს ძველ ვერსიებზე მოსალოდნელზე მეტი უფლება.
MongoDB-ში იგივე იდეა ერთი ბაზით შემოფარგლულ ჩაშენებულ როლებს იყენებს:
db.createUser({ user: "app_rw", pwd: passwordPrompt(), roles: [ { role: "readWrite", db: "app" } ]})აპლიკაციას არასოდეს მისცე root, dbOwner ან რამე, რაც AnyDatabase-ით მთავრდება. შემდეგ კავშირის სტრიქონს authSource სჭირდება იმ ბაზაზე მითითებით, სადაც მომხმარებელი შეიქმნა, და ეს არის ყველაზე გავრცელებული მიზეზი, რის გამოც სწორი პაროლი უარყოფილია - MongoDB-ის connection string-ები მთელ აგებულებას აჩვენებს.
იგივე პრინციპი ადამიანებზეც მოქმედებს. პანელის ანგარიშებს, deploy გასაღებებსა და დაფებს თითოეულს უნდა ჰქონდეს ყველაზე ვიწრო როლი, რომელიც სამუშაოს ასრულებინებს, და დასახელებული მფლობელი. Subuser-ები და მინიმალური უფლებები ამ მხარეს განიხილავს.
დაშიფრე კავშირი და გადაამოწმე სერტიფიკატი#
თუ ბაზა და აპლიკაცია ერთ მანქანაზე არ არის, ავტორიზაციის მონაცემები და ყოველი სტრიქონი ქსელს გადის. მისი დაშიფვრა ადვილია. ისე დაშიფვრა, რომ ნამდვილად დაამტკიცოს, ვის დაუკავშირდი, ერთ დამატებით ნაბიჯს მოითხოვს, და კავშირის სტრიქონების უმეტესობა მანამდე ჩერდება.
PostgreSQL-ის sslmode-ს ექვსი მნიშვნელობა აქვს და მხოლოდ ორი ღირს გამოსაყენებლად:
sslmode | შიფრავს | სერვერს ამოწმებს | ვერდიქტი |
|---|---|---|---|
disable | არა | არა | ღია ტექსტი |
allow, prefer | შესაძლოა | არა | prefer ნაგულისხმევია და ჩუმად უკან ბრუნდება |
require | დიახ | არა | დაუცველია შუაში მდგარი მანქანის მიმართ |
verify-ca | დიახ | მხოლოდ სერტიფიკატების ჯაჭვს | მისაღებია |
verify-full | დიახ | ჯაჭვსა და hostname-ს | გამოიყენე ეს |
postgresql://app_rw:pw@db.example.net:5432/app?sslmode=verify-full&sslrootcert=/etc/ssl/certs/ca.pemMongoDB-ზე იგივე არგუმენტია, სხვა დაწერილობით: tls=true URI-ში, tlsCAFile, თუ სერტიფიკატი საჯარო ორგანოსგან არ არის, და არასოდეს tlsAllowInvalidCertificates=true, რომელიც ვერიფიკაციას თიშავს, ხოლო სტრიქონში სიტყვა TLS გამშვიდებისთვის რჩება.
ავტორიზაციის მონაცემები კოდს მოაცილე#
ბაზის პაროლი git რეპოზიტორიაში არის პაროლი ინტერნეტში, რეპოზიტორია საჯაროა თუ არა - ის რჩება ისტორიაში, fork-ებში, ყველას laptop-ის backup-ებში, ვინც ოდესმე clone გააკეთა, და იმ ინდექსებში, რომლებსაც შენი git ჰოსტი ინახავს. ავტორიზაციის მონაცემები ეკუთვნის გარემოს ცვლადებს ან secrets საცავს, runtime-ზე შეყვანილს, და .env უნდა იყოს .gitignore-ში პირველი commit-დან და არა იმ დღიდან, როცა შენიშნავ.
სამი პრაქტიკული წესი:
- პაროლი არასოდეს ჩაწერო command line-ზე. ის ხვდება shell-ის ისტორიაში და მანქანის ყველა მომხმარებლისთვის
ps-ის გამოტანაში. გამოიყენე~/.pgpassრეჟიმით0600,PGPASSWORDგარემოში, ან მოთხოვნა. - staging-სა და production-ს ყოველთვის სხვადასხვა ავტორიზაციის მონაცემები ჰქონდეს. staging მანქანა განსაზღვრებით ნაკლებად სანდოა, და საერთო პაროლი მას შესასვლელად აქცევს.
- როცა ვინმე გუნდს ტოვებს, შეცვალე. გაუქმებული წვდომა და დავიწყებული წვდომა ერთი და იგივე არ არის.
გარემოს ცვლადები და secrets განიხილავს მექანიკას პანელის ჰოსტზე, სადაც Startup ჩანართი მნიშვნელობებს ინახავს და ისინი image-შიც და რეპოზიტორიაშიც არ არის.
მოთხოვნები, რომლებშიც injection ვერ შეაღწევს#
Injection ათწლეულების შემდეგაც ყველაზე გავრცელებული გზაა, რომლითაც ბაზას კითხულობს ის, ვისაც არ უნდა ეკითხა. მექანიზმი ყოველთვის ერთია: მომხმარებლის შეყვანა მოთხოვნას ეწებება, ამიტომ შეყვანას შეუძლია მოთხოვნის სტრუქტურა შეცვალოს და არა მხოლოდ მისი მნიშვნელობები.
// Broken. An email of ' OR 1=1 -- returns every user.const sql = "SELECT * FROM users WHERE email = '" + email + "'";// Correct. The driver sends the query and the value separately.await client.query("SELECT * FROM users WHERE email = $1", [email]);# psycopg, the same idea. The second argument is never string formatting.cur.execute("SELECT * FROM users WHERE email = %s", (email,))პარამეტრიზებული მოთხოვნები escaping-ის ხრიკი არ არის, ისინი პროტოკოლის შესაძლებლობაა: ჯერ განცხადება იპარსება, შემდეგ მნიშვნელობები უერთდება, ამიტომ მნიშვნელობა სინტაქსად ვერ იქცევა. გამოიყენე ისინი ყველგან, იმ მოთხოვნისთვისაც კი, რომელიც აშკარად უსაფრთხოდ მიგაჩნია.
ორი რამ, რასაც პარამეტრები ვერ აკეთებს და სადაც injection უკან ბრუნდება:
- იდენტიფიკატორები. ცხრილისა და სვეტის სახელები პარამეტრები ვერ იქნება. თუ დალაგების სვეტი query string-იდან მოდის, შეადარე ცნობილი სვეტების სახელების დაშვებულ სიას და დანარჩენი უარყავი. არასოდეს ჩასვა პირდაპირ.
- მთელი clause-ები.
WHEREფრაგმენტების სტრიქონების შეერთებით აწყობა იგივე შეცდომაა უკეთეს ტანსაცმელში, ORM-ით იქნება თუ მის გარეშე. ORM-ების უმეტესობა ნაგულისხმევად უსაფრთხოა და აღარ არის, როგორც კი raw-SQL გასასვლელს გამოიყენებ.
MongoDB-ს საკუთარი ვერსია აქვს. თუ JSON მოთხოვნის სხეული პირდაპირ query დოკუმენტში გადადის, კლიენტს შეუძლია გამოგზავნოს {"password": {"$ne": null}} და ყველა ჩანაწერს დაემთხვეს. შემომავალი მნიშვნელობები დააკასტე მოსალოდნელ ტიპზე, სანამ მოთხოვნამდე მივა, და გამორთე სერვერზე JavaScript-ის შესრულება, იმის იმედად ყოფნის ნაცვლად, რომ $where ფრთხილად გამოიყენება.
ეს თავი იმიტომ დგას მინიმალური უფლებების შემდეგ, რომ ეს ორი ერთმანეთს ემატება. injection ანგარიშზე, რომელსაც მხოლოდ სამი ცხრილიდან SELECT შეუძლია, ცუდი დღეა. იგივე injection superuser-ზე კი ფაილების ჩაწერასა და ბრძანებების გაშვებას შეძლებს.
ლიმიტები, რომ ერთმა კლიენტმა ბაზა ვერ დააგდოს#
ხელმისაწვდომობა უსაფრთხოების ნაწილია, და პატარა ბაზის გათიშვის ყველაზე ადვილი გზაა მასთან იმდენი კავშირის გახსნა, სანამ თავისუფალი აღარ დარჩება.
- `max_connections` PostgreSQL-ში მკაცრი ზღვარია, და თითოეული კავშირი მეხსიერებას ხარჯავს, მუშაობს თუ უსაქმოდაა. 1-2 GB ინსტანციაზე 100 უკვე ოპტიმისტურია. წინ pooler დააყენე და თითოეულ აპლიკაციას საკუთარი
CONNECTION LIMITმიეცი ბრძანებითALTER ROLE app_rw CONNECTION LIMIT 20;. Connection pool-ები და ლიმიტები არითმეტიკას განმარტავს. - `statement_timeout` ერთ გაუკონტროლებელ მოთხოვნას რესურსების სამუდამოდ დაკავებას უშლის. დააყენე როლის დონეზე და არა გლობალურად, რომ ანგარიშგების მომხმარებელს ვებ აპლიკაციაზე უფრო გრძელი ლიმიტი ჰქონდეს:
ALTER ROLE app_rw SET statement_timeout = '15s'; - `idle_in_transaction_session_timeout` კლავს სესიებს, რომლებმაც ტრანზაქცია გახსნეს და წავიდნენ. ისინი vacuum-ს ბლოკავენ და lock-ებს იჭერენ, და შეცდომიანი ვებ აპლიკაცია მათ ასობით აწარმოებს.
- Rate limit-ები აპლიკაციაში ყველაფრის წინ, რაც ანონიმური ვიზიტორისთვის ძვირ მოთხოვნას ასრულებს. ძიების endpoint-ები ჩვეულებრივი მსხვერპლია. Rate limit-ები და abuse ამ შაბლონს აღწერს.
Backup, რომელიც რეალურად აღგიდგენია#
Backup, რომელიც არავის აღუდგენია, ჰიპოთეზაა. ეს ჩეკლისტის ბოლო ხაზია და ის, რომელიც წყვეტს, ინციდენტი უხერხულობა იქნება თუ ბიზნესის დასასრული.
- ავტომატური, გრაფიკით. ხელით backup ის backup-ია, რომელსაც დატვირთულ კვირას შეწყვეტ, როცა ყველაზე მეტად დაგჭირდება.
- იმ მანქანის გარეთ, რომელსაც იცავს. იმავე დისკზე ასლი წაშლილ ცხრილს გადაურჩება და სხვა არაფერს.
- ერთზე მეტი თაობა. გაფუჭება და მავნე ცვლილებები ხშირად დღეების შემდეგ შეინიშნება, და ერთი ღამის ასლი იმ დროისთვის კარგს გადაფარავს.
- აღდგენილი განზრახ, შენთვის მოსახერხებელ დროს. თვეში ან ორ თვეში ერთხელ აღადგინე დროებით ბაზაში, დათვალე რამდენიმე სტრიქონი, გაუშვი აპლიკაცია მასზე. ეს ერთადერთი მტკიცებულებაა, რომ backup მუშაობს.
- განიხილება როგორც მგრძნობიარე. dump ფაილი მთელი ბაზაა ერთ ჩამოსატვირთ ობიექტში. ნუ დატოვებ მას ვებიდან ხელმისაწვდომ დირექტორიაში, რაც გასაკვირად გავრცელებული გზაა, რომლითაც საიტები ყველაფერს ჟონავენ.
Database backup-ები და აღდგენა ძრავების მიხედვით მექანიკას აჩვენებს, ხოლო აღდგენის შემოწმება, სანამ დაგჭირდება ნახევარია, რომელსაც ხალხი ტოვებს. თუ უარესი უკვე მოხდა, რა უნდა გააკეთო, როცა შენი სერვერი გატეხეს აღწერს, რა თანმიმდევრობით იმოქმედო, და ეს ინსტინქტის გამოთქმულ თანმიმდევრობას არ ემთხვევა.
ლოგები, რომლებიც აჩვენებს, რა შეიცვალა#
შეუძლებელია იმის გამოძიება, რაც არ ჩაგიწერია, და უმეტეს ბაზაზე ნაგულისხმევი ლოგირება თითქმის არაფერ სასარგებლოს წერს.
log_connections = onlog_disconnections = onlog_statement = 'ddl'log_min_duration_statement = 1000log_line_prefix = '%m [%p] %q%u@%d from %h 'log_statement = 'ddl' წერს ყოველ სქემის ცვლილებას ყველა მოთხოვნის ლოგირების ხმაურის გარეშე. log_min_duration_statement = 1000 წერს ყველაფერს, რაც წამზე ნელია, და ამავდროულად შენი წარმადობის მონაცემია. %h პრეფიქსში ყოველ ხაზზე კლიენტის მისამართს ათავსებს, რაც პირველი გჭირდება, როცა რაღაც უცნაურად გამოიყურება.
ძრავის გარდა, თვალი ადევნე იმას, რაც იშვიათად იცვლება: ახალი როლი, შეცვლილი pg_hba.conf, მომხმარებელი, რომელსაც უფრო მეტი უფლება მიეცა, ვიდრე ჰქონდა. ესენი პატარა მოვლენებია დიდი მნიშვნელობით. ლოგები, რომლებიც ღირს შენახვად შენახვის ვადებს განიხილავს, ხოლო პანელის თითოეული სერვერის activity ლოგი ანგარიშის მხარეს წერს, ვინ რა გააკეთა.
ჩეკლისტი#
გაიარე ერთხელ ყოველი ბაზისთვის, რომელსაც უშვებ. რასაც ვერ უპასუხებ, ის არის შემდეგი გასასწორებელი.
| # | შემოწმება | დასრულებულია, როცა |
|---|---|---|
| 1 | პორტი მთელ მსოფლიოს არ ეხსნება | ss -ltnp და გარე პორტის ტესტი ერთმანეთს ემთხვევა |
| 2 | ავტენტიფიკაცია ჩართულია და SCRAM-ს იყენებს | არც trust, არც md5, არც MongoDB authorization-ის გარეშე |
| 3 | პაროლები გენერირებულია და უნიკალურია | არაფერი მეორდება გარემოებს შორის |
| 4 | აპლიკაციის მომხმარებელს სქემის შეცვლა არ შეუძლია | მიგრაციები სხვა როლით სრულდება |
| 5 | ადამიანებისა და დაფებისთვის არსებობს მხოლოდ წაკითხვის როლი | არავინ იყენებს owner-ს მოთხოვნებისთვის |
| 6 | კავშირები დაშიფრულია და გადამოწმებულია | verify-full ან tls=true CA ფაილით |
| 7 | ავტორიზაციის მონაცემები რეპოზიტორიის გარეთაა | .env იგნორირებულია, მნიშვნელობები runtime-ზე შედის |
| 8 | ყველა მოთხოვნა პარამეტრიზებულია | სტრიქონების შეერთების გარეშე, იდენტიფიკატორები დაშვებულ სიაშია |
| 9 | კავშირებისა და მოთხოვნების ლიმიტები დაყენებულია | ერთ ცუდ კლიენტს სერვერის ამოწურვა არ შეუძლია |
| 10 | Backup-ები ავტომატურია, მანქანის გარეთაა და შემოწმებულია | ამ კვარტალში ერთი აღადგინე |
| 11 | კავშირები, DDL და ნელი მოთხოვნები ილოგება | გუშინდელის აღდგენა შეგიძლია |
| 12 | ძრავისა და დრაივერის ვერსიები აქტუალურია | უსაფრთხოების გამოშვებები კვირებში ინერგება |
FAQ#
საკმარისია ძალიან გრძელი პაროლი, თუ პორტი საჯაროა?
ის გამოცნობას აჩერებს და სხვა არაფერს. საჯარო პორტი მაინც გხდის დაუცველს იმ ვერსიის პროტოკოლის დონის შეცდომების მიმართ, რასაც უშვებ, სხვაგან გაჟონილი ავტორიზაციის მონაცემების მიმართ და კავშირების ამოწურვით service-ის შეფერხების მიმართ. ამიტომ წყარო მისამართებიც შეზღუდე, ყოველთვის. ავტენტიფიკაცია და ხელმისაწვდომობა ცალკე კონტროლებია და ორივე გჭირდება.
მჭირდება TLS, თუ ბაზა კერძო ქსელშია?
მაინც გამოიყენე. კერძო ქსელები სხვა tenant-ებთანაც ისე ხშირად იზიარება, როგორც სახელი არ ეტყობა, გატეხილ შიდა მანქანას ტრაფიკის მოსმენა შეუძლია, და TLS-ის ჩართვის ფასი კავშირის სტრიქონში ერთი ხაზია. ერთადერთი, რაზეც ფრთხილად უნდა იყო, ვერიფიკაციაა: require verify-full-ის გარეშე შიფრავს, მაგრამ არ ამტკიცებს, ვინ უპასუხა.
იცავს ORM SQL injection-ისგან?
იმ მოთხოვნებისთვის, რომლებსაც თვითონ აგებს - დიახ. რისკი არის raw query მეთოდი, რომელსაც ყველა ORM გთავაზობს, და ის ადგილები, სადაც იდენტიფიკატორი ან clause სტრიქონად აიწყო, რადგან query builder-მა ის ვერ გამოხატა. ეს ხაზებია გადასახედი, და ისინი ჩვეულებრივ ისაა, რომლებზეც კომენტარი ბოდიშს იხდის.
რა სიხშირით უნდა შეიცვალოს ბაზის პაროლები?
გრაფიკით შეცვლა ძირითადად თეატრია. მოვლენის შემთხვევაში კი აუცილებელია: ვინმე მიდის, ავტორიზაციის მონაცემი ლოგში ან screenshot-ში გამოჩნდა, მანქანა, რომელსაც ის ჰქონდა, გატეხეს, ან მესამე მხარის ხელსაწყოს, რომელსაც წვდომა მისცემდი, ინციდენტი დაემართა. ცვლილება ისე მოაწყვე, რომ ხუთ წუთს იღებდეს და deploy არ სჭირდებოდეს, და გააკეთებ მაშინ, როცა მნიშვნელოვანია.
რა არის ამ სიაში ერთადერთი ყველაზე ღირებული რამ?
ხელმისაწვდომობის შეზღუდვა, შემდეგ კი შემოწმებული backup. პირველი თავიდან გვარიდებს იმას, რაც ხშირად ხდება; მეორე ნიშნავს, რომ დანარჩენს გადაურჩები. ყველაფერი დანარჩენი ამ ორს შორის ზიანს ავიწროვებს.
ჩემი ჰოსტი ბაზას მართავს. ამისგან რა რჩება ჩემი?
ხელმისაწვდომობა, ძრავის patch-ები და ქვედა მანქანა ჰოსტისაა. ვისაც შექმნი, რა უფლებებს მისცემ, ამოწმებს თუ არა კავშირის სტრიქონი TLS-ს, პარამეტრიზებულია თუ არა მოთხოვნები, სად ინახება ავტორიზაციის მონაცემები და აღგიდგენია თუ არა ოდესმე backup - ყველაფერი შენია, და ინციდენტები სწორედ აქედან მოდის.




კომენტარები
სრულიად ანონიმურად: ანგარიშის, ელფოსტის და cookie-ის გარეშე. ინახება მხოლოდ სახელი, ტექსტი და დრო - სხვა არაფერი. ბმულების რაოდენობა ლიმიტირებულია.