جعل تحليل Kubernetes RBAC سهلًا
Krane هي أداة تحليل ثابت Kubernetes RBAC. إنه يحدد المخاطر الأمنية المحتملة في تصميم K8S RBAC ويقدم اقتراحات حول كيفية التخفيف منها. تقدم Krane Dashboard وضعية أمان RBAC الحالية وتتيح لك التنقل من خلال تعريفها.
يمكنك البدء في Krane عن طريق تثبيته عبر مخطط Helm في مجموعة Kubernetes المستهدفة أو تشغيلها محليًا مع Docker.
من المفترض أن يكون لديك Helm CLI مثبتة على جهازك.
$ helm repo add appvia https://appvia.github.io/krane
$ helm repo update
$ helm install krane appvia/krane --namespace krane --create-namespaceاتبع إخراج تثبيت مخطط Helm على كيفية تشغيل لوحة معلومات Krane.
من المفترض أن يكون لديك Docker يعمل على جهازك المحلي. قم بتثبيت Docker-Cormse إذا لم تكن قد لم تكن بالفعل.
كرين يعتمد على redisgraph. يحدد docker-compose Stack كل ما هو مطلوب لإنشاء وتشغيل خدمة Krane محليًا. سوف يعتني أيضًا بتهمة redisgraph.
docker-compose up -d
سيتم بناء صورة Krane Docker تلقائيًا إن لم تكن موجودة بالفعل على الجهاز المحلي.
لاحظ أنه عند تشغيل docker-compose محليًا ، لن يبدأ Krane تقرير RBAC ولوحة القيادة تلقائيًا. بدلاً من ذلك ، ستنام الحاوية لمدة 24 ساعة افتراضيًا - يمكن ضبط هذه القيمة في docker-compose.override.yml . exec في حاوية krane قيد التشغيل لتشغيل الأوامر. سوف يقوم docker-compose المحلي أيضًا بتثبيت kube config ( ~/.kube/config ) داخل الحاوية مما يتيح لك تشغيل التقارير مقابل أي مجموعات Kubernetes التي يمكنك الوصول إليها بالفعل.
exec في حاوية krane قيد التشغيل.
docker-compose exec krane bash بمجرد الوصول إلى الحاوية ، يمكنك البدء في استخدام أوامر krane . جرب krane -help .
krane -hلتفقد الخدمات التي يتم تشغيلها والموانئ المرتبطة بها:
docker-compose ps
لوقف كرين وخدمات التبعية:
docker-compose down
$ krane --help
NAME:
krane
DESCRIPTION:
Kubernetes RBAC static analysis & visualisation tool
COMMANDS:
dashboard Start K8s RBAC dashboard server
help Display global or [command] help documentation
report Run K8s RBAC report
GLOBAL OPTIONS:
-h, --help
Display help documentation
-v, --version
Display version information
-t, --trace
Display backtrace when an error occurs
AUTHOR:
Marcin Ciszak <[email protected]> - Appvia Ltd <appvia.io>
kubectl المحليلتشغيل تقرير مقابل مجموعة قيد التشغيل ، يجب عليك توفير سياق KUBECTL
krane report -k <context>
يمكنك أيضًا تمرير علامة -c <cluster-name> إذا كنت تخطط لتشغيل الأداة مقابل مجموعات متعددة وفهرسة RBAC الرسم البياني بشكل منفصل لكل اسم مجموعة.
لتشغيل تقرير ضد ملفات RBAC YAML/JSON المحلية ، قدم مسار الدليل
krane report -d </path/to/rbac-directory>
ملاحظة: تتوقع Krane أن تكون الملفات التالية (بتنسيق Yaml أو JSON) موجودًا في مسار الدليل المحدد:
إذا لم تكن سياسات أمان POD قيد الاستخدام ، فيمكنك تجاوز التوقعات أعلاه عن طريق إنشاء ملف psp يدويًا مع المحتوى التالي:
{
"items" : []
} ملاحظة ، تم إهمال PodSecurityPolicy في Kubernetes v1.21 ، وتم إزالته من Kubernetes في V1.25.
لتشغيل تقرير من حاوية تعمل في مجموعة Kubernetes
krane report --incluster
ملاحظة: سيتطلب حساب الخدمة المستخدمة من قبل Krane الوصول إلى موارد RBAC. انظر المتطلبات الأساسية للحصول على التفاصيل.
للتحقق من صحة تعريف RBAC كخطوة في خط أنابيب CI/CD
krane report --ci -d </path/to/rbac-directory>
ملاحظة: يتوقع Krane اتباع بعض اتفاقية التسمية بعضها لملفات RBAC المخزنة محليًا. انظر القسم أعلاه. من أجل تشغيل أوامر krane ، يوصى بأن CI Executor References Quay.io/appvia/Krane:Latest Docker Image.
يتم تمكين وضع CI بواسطة --ci flag. ستعيد Krane رمز الحالة غير الصفر إلى جانب تفاصيل كسر قواعد المخاطر عند اكتشاف مخاطر أو أكثر.
لعرض شجرة RBAC ، الرسم البياني للشبكة وأحدث نتائج التقارير التي تحتاجها لبدء تشغيل خادم لوحة القيادة أولاً.
krane dashboard
قد يتم تمرير علامة المجموعة -c <cluster-name> إذا كنت ترغب في تشغيل لوحة القيادة مقابل اسم الكتلة المحدد. ستبحث لوحة القيادة عن البيانات المتعلقة باسم الكتلة المحدد الذي تم تخزينه مؤقتًا على نظام الملفات.
سيبدأ الأمر أعلاه خادم الويب المحلي على المنفذ الافتراضي 8000 ، ويعرض رابط لوحة القيادة.
krane فهارس RBAC ents في redisgraph. يتيح لنا ذلك الاستعلام عن شبكة التبعيات بكفاءة وببساطة باستخدام مجموعة فرعية من CypherQL بدعم من RedisGraph.
يتم إنشاء العقد التالية في الرسم البياني لكائنات RBAC ذات الصلة:
Psp - عقدة PSP تحتوي على سمات حول سياسة أمان POD. ينطبق فقط عند العمل مع K8S <1.25.Rule - تمثل عقدة القاعدة قاعدة التحكم في الوصول حول موارد Kubernetes.Role - تمثل عقدة الدور دورًا معينًا أو مجموعة. السمة kind تحدد نوع الدور.Subject - يمثل الموضوع جميع الجهات الفاعلة الممكنة في الكتلة ( kind : المستخدم والمجموعة و serviceaccount)Namespace - عقدة مساحة الاسم Kubernetes. :SECURITY - يحدد رابطًا بين القاعدة وعقد PSP. ينطبق فقط عند العمل مع K8S <1.25.:GRANT - يحدد صلة بين الدور والقاعدة المرتبطة بهذا الدور.:ASSIGN - يحدد رابطًا بين الممثل (الموضوع) والدور المعطى/الكتلة (عقدة الدور).:RELATION - تحدد صلة بين اثنين من الممثلين المختلفين (الموضوع).:SCOPE - يحدد رابطًا بين الدور وعقد مساحة الاسم.:ACCESS - يحدد رابطًا بين العقد ومساحة الاسم.:AGGREGATE -يحدد وجود صلة بين المجموعات (مجموعة مجموعات واحدة تجمع آخر) A-(aggregates)->B:COMPOSITE -يحدد وجود صلة بين مجموعات المجموعات (يمكن تجميع مجموعة واحدة في أخرى) A<-(is a composite of)-B جميع الحواف ثنائية الاتجاه ، مما يعني أنه يمكن الاستعلام عن الرسم البياني في أي من الاتجاهين. الاستثناءات فقط هي :AGGREGATE و :COMPOSITE التي هي أحادية الاتجاه ، على الرغم من أنها مهتمة بنفس العقد الحافة.
من أجل الاستعلام عن الرسم البياني مباشرة ، يمكنك التنفيذ في حاوية redisgraph قيد التشغيل ، وبدء redis-cli وقم بتشغيل استعلاماتك التعسفية. اتبع التعليمات الرسمية لأمثلة من الأوامر.
يمكنك أيضًا الاستعلام عن الرسم البياني من وحدة التحكم Krane . أول exec في تشغيل حاوية krane ، ثم
# Start Krane console - this will open interactive ruby shell with Krane code preloaded
console
# Instantiate Graph client
graph = Krane :: Clients :: RedisGraph . client cluster : 'default'
# Run arbitrary CypherQL query against indexed RBAC Graph
res = graph . query ( %Q(
MATCH (r:Rule {resource: "configmaps", verb: "update"})<-[:GRANT]-(ro:Role)<-[:ASSIGN]-(s:Subject)
RETURN s.kind as subject_kind, s.name as subject_name, ro.kind as role_kind, ro.name as role_name) )
# Print the results
res . print_resultset # Results...
+----------------+--------------------------------+-----------+------------------------------------------------+
| subject_kind | subject_name | role_kind | role_name |
+----------------+--------------------------------+-----------+------------------------------------------------+
| ServiceAccount | bootstrap-signer | Role | system:controller:bootstrap-signer |
| User | system:kube-controller-manager | Role | system::leader-locking-kube-controller-manager |
| ServiceAccount | kube-controller-manager | Role | system::leader-locking-kube-controller-manager |
| User | system:kube-scheduler | Role | system::leader-locking-kube-scheduler |
| ServiceAccount | kube-scheduler | Role | system::leader-locking-kube-scheduler |
+----------------+--------------------------------+-----------+------------------------------------------------+
ملاحظة: سيحدد الاستعلام مثال أعلاه جميع المواد ذات الأدوار/المجموعات المعينة التي تمنح الوصول إلى update configmaps .
يتم تعريف قواعد مخاطر RBAC في ملف القواعد. بنية كل قاعدة إلى حد كبير واضحة للذات. يمكن توسيع / تجاوز المجموعة المضمنة عن طريق إضافة قواعد مخصصة إضافية إلى ملف قواعد Cutom.
وحدات الماكرو هي "حاويات" لمجموعة من السمات الشائعة/المشتركة ، ويشار إليها بقواعد مخاطرة واحدة أو أكثر. إذا اخترت استخدام الماكرو في قاعدة مخاطر معينة ، فستحتاج إلى الرجوع إليها بالاسم ، على سبيل المثال macro: <macro-name> . لاحظ أن السمات المحددة في macro المشار إليها ستتخذ الأسبقية على نفس السمات المحددة على مستوى القاعدة.
يمكن أن يحتوي الماكرو على أي من السمات التالية:
query - redisgraph استعلام. له الأسبقية على template . يتطلب أن يتم تعريف writer .writer - الكاتب هو تعبير روبي يستخدم لتنسيق مجموعة نتائج query . الكاتب له الأسبقية على template .template - الاستعلام المدمج/الكاتب اسم. إذا لم يتم تحديد query writer ، فسيتم استخدام مولد الاستعلام المختار مع الكاتب المطابق. يمكن أن تحتوي القاعدة على أي من السمات التالية:
id [مطلوب] معرف القاعدة هو معرف قاعدة فريد.
group_title [مطلوب] عنوان ينطبق على جميع العناصر التي تقع تحت فحص المخاطر هذا.
severity [المطلوبة] شدة ، كواحدة من: الخطر ،: تحذير ،: معلومات.
info [مطلوبة] معلومات نصية حول الشيك والاقتراحات حول كيفية التخفيف من المخاطر.
query [conditonal] redisgraph query.
template . يتطلب أن يتم تعريف writer . writer [conditonal] الكاتب هو تعبير روبي يستخدم لتنسيق مجموعة نتائج الاستعلام.
template . يتطلب أن يتم تحديد query . template [conditonal] الاستعلام المدمج/قالب الكاتب. إذا لم يتم تحديد query writer ، فسيتم استخدام مولد الاستعلام المختار مع الكاتب المطابق.
تتطلب بعض القوالب المدمجة سمة match_rules لتحديدها على مستوى القاعدة الفردية من أجل إنشاء استعلام صحيح. قوالب تتطلب ذلك حاليا:
match_rules . إرجاع استعلام الرسم البياني الذي تم إنشاؤه الأعمدة التالية: match_rules [conditonal] المطلوبة عندما يعتمد template على قواعد المطابقة من أجل بناء استعلام.
match_rules :
- resources : ['cronjobs']
verbs : ['update'] custom_params [اختياري] قائمة أزواج القيمة الرئيسية المخصصة التي سيتم تقييمها واستبدالها في query القاعدة وتمثيل writer .
custom_params :
- attrA : valueA
- attrB : valueB{{attrA}} و {{attrB}} بـ valueA و valueB على التوالي. threshold [اختيارية] القيمة الرقمية. عندما يتم تحديد ذلك ، سيصبح هذا متاحًا كقالب نائبة {{threshold}} في تعبير writer .
مرجع macro [اختياري] إلى المعلمات الشائعة المحددة في ماكرو اسمه.
disabled [اختياري] عند التعيين على true سوف يعطى القاعدة المعطاة واستبعادها من التقييم. بشكل افتراضي يتم تمكين جميع القواعد.
- id : verbose-rule-example
group_title : Example rule
severity : :danger
info : Risk description and instructions on how to mitigate it goes here
query : |
MATCH
(s:Subject)-[:ACCESS]->(ns:Namespace)
WHERE
NOT s.name IN {{whitelist_subject_names}}
RETURN
s.kind as subject_kind,
s.name as subject_name,
COLLECT(ns.name) as namespace_names
ORDER BY
subject_kind,
subject_name,
namespace_names DESC
threshold : 2
writer : |
if result.namespace_names.count > {{threshold}}
"#{result.subject_kind} #{result.subject_name} can access namespaces: #{result.namespace_names.join(', ')}"
end
disabled : true يحدد المثال أعلاه صراحة query الرسم البياني الذي يتم استخدامه لتقييم مخاطر RBAC ، وتعبير writer يستخدم لتنسيق مجموعة نتائج الاستعلام. يختار الاستعلام ببساطة جميع Subjects (باستثناء القائمة البيضاء) ومساحات Namespaces التي يمكنهم الوصول إليها. لاحظ أن مجموعة النتائج ستتضمن فقط Subjects التي تتمتع بالوصول إلى أكثر من 2 أسماء (تم ملاحظة قيمة threshold هناك؟). سيتم التقاط تعبير writer الأخير كإخراج عنصر النتيجة المنسقة.
يمكن writer الوصول إلى عنصر مجموعة النتائج عبر كائن result مع طرق مطابقة عناصر إرجاعها عن طريق الاستعلام ، على سبيل المثال result.subject_kind ، result.subject_name إلخ.
ملحوظة:
{{threshold}} سيتم استبدال العنصر الوطني في تعبير writer بقيمة الكلمة الرئيسية threshold القاعدة.{{whitelist_subject_names}} حقلًا مخصصًا سيتم تحريفه بقيم القائمة البيضاء المحددة id قاعدة معين. إذا لم يتم تعريف اسم حقل العنصر النائب في القائمة البيضاء ، فسيتم استبداله بمصفوعة فارغة [''] افتراضيًا. اقرأ المزيد عن القائمة البيضاء أدناه. تقوم القوالب المدمجة بتبسيط تعريف قاعدة المخاطر بشكل كبير ، ومع ذلك ، فقد تم تصميمها لاستخراج نوع معين من المعلومات وقد لا يكون مناسبًا لقواعدك المخصصة. إذا وجدت نفسك تعيد استخدام تعبيرات query أو writer عبر قواعد متعددة ، فيجب عليك التفكير في استخراجها إلى macro والرجوع إليها في قواعدك المخصصة لتجفيفها.
- id : risky-any-verb-secrets
group_title : Risky Roles/ClustersRoles allowing all actions on secrets
severity : :danger
info : Roles/ClusterRoles allowing all actions on secrets. This might be dangerous. Review listed Roles!
template : risky-role
match_rules :
- resources : ['secrets']
verbs : ['*'] مثال أعلاه يوضح إحدى القواعد المدمجة. يشير إلى أن قالب risky-role والذي سيقوم عند المعالجة بتوسيع القاعدة عن طريق حقن تعبيرات query writer قبل أن تُعتبر تقييم القاعدة. سيتم استخدام match_rules لبناء استعلام مطابقة مناسب.
يحتوي القائمة البيضاء الاختيارية على مجموعة من أسماء السمات المحددة المخصصة والقيم المعنية (القائمة البيضاء).
أسماء السمات وقيمها تعسفية. يتم تعريفها في ملف القائمة البيضاء وتقسيمها إلى ثلاثة أقسام منفصلة:
global - نطاق المستوى الأعلى. ستطبق السمات المخصصة المحددة هنا على جميع قواعد المخاطر بغض النظر عن اسم الكتلة.common - سيتم تحديد سمات مخصصة لمعرف قاعدة المخاطر id بغض النظر عن اسم الكتلة.cluster (مع قائمة متداخلة لأسماء المجموعات) - سيتم تطبيق السمات المخصصة على id قاعدة المخاطر المحددة لاسم كتلة معين. ستحاول كل قاعدة للمخاطر ، عند التقييم ، استيفاء جميع العناصر النائبة المعلمة المستخدمة في query ، على سبيل المثال {{your_whitelist_attribute_name}} . إذا كان اسم معلمة العنصر النائب (أي الاسم بين الأقواس المجعد المزدوجة) يطابق أيًا من أسماء السمات ذات القائمة البيضاء id قاعدة المخاطرة هذا ، فسيتم استبداله بقيمة محسوبة. إذا لم يتم العثور على قيم لعنصر نائب معين ، فسيتم استبداله بـ [''] .
مثال على ذلك القائمة البيضاء أدناه ينتج placeholder-key => value لقاعدة مخاطر مع قيمة سمة id التي تتطابق مع "معرف القسمة المعرضة للخطر"
{{whitelist_role_names}} => ['acp:prometheus:operator']
{{whitelist_subject_names}} => ['privileged-psp-user', 'another-user']
سيتم استبدال مفاتيح العنصر النائب أعلاه ، عند استخدامها في استعلامات الرسم البياني المخصص ، بقيمها الخاصة عند تقييم قواعد المخاطر.
مثال:
---
rules :
global : # global scope - applies to all risk rule and cluster names
whitelist_role_names : # custom attribute name
- acp:prometheus:operator # custom attribute values
common : # common scope - applies to specific risk rule id regardless of cluster name
some-risk-rule-id : # this corresponds to risk rule id defined in config/rules.yaml
whitelist_subject_names : # custom attribute name
- privileged-psp-user # custom attribute values
cluster : # cluster scope - applies to speciifc risk rule id and cluster name
default : # example cluster name
some-risk-rule-id : # risk rule id
whitelist_subject_names : # custom attribute nane
- another-user # custom attribute values يمكن نشر Krane في مجموعات Kubernetes المحلية أو النائية بسهولة.
مساحة اسم Kubernetes ، يجب أن يكون حساب الخدمة جنبًا إلى جنب مع RBAC المناسب موجودًا في المجموعة. انظر المتطلبات الأساسية للرجوع إليها.
تنفذ نقطة إدخال krane الافتراضية Bin/Insuster والتي تنتظر أن تصبح مثيل RedisGraph متاحًا قبل بدء تشغيل حلقة تقرير RBAC وخادم الويب.
يمكنك التحكم في جوانب معينة من التنفيذ داخل الكتلة مع متغيرات البيئة التالية:
KRANE_REPORT_INTERVAL - يحدد الفاصل الزمني في ثوانٍ لتقرير تقرير التحليل الثابت RBAC. الافتراضي: 300 (في ثوان ، أي 5 دقائق).KRANE_REPORT_OUTPUT - يحدد تنسيق الإخراج RBAC لمخاطر RBAC. القيم الممكنة :json ، :yaml ، :none . الافتراضي :: :json .قبل أن نبدأ ، ستحتاج إلى الأدوات التالية:
تثبيت مخطط Helm:
$ helm repo add appvia https://appvia.github.io/krane
$ helm repo update
$ helm install krane appvia/krane --namespace krane --create-namespaceراجع ملف القيم. yaml للحصول على تفاصيل الخيارات والمعلمات القابلة للتسوية الأخرى.
kubectl create
--context < docker-desktop >
--namespace krane
-f k8s/redisgraph-service.yaml
-f k8s/redisgraph-deployment.yaml
-f k8s/krane-service.yaml
-f k8s/krane-deployment.yamlلاحظ أن خدمة Krane Dashboard لا تتعرض افتراضيًا!
kubectl port-forward svc/krane 8000
--context= < docker-desktop >
--namespace=krane
# Open Krane dashboard at http://localhost:8000يمكنك العثور على مثال النشر في دليل K8S.
تعديل البيان كما هو مطلوب لنشراتك مع التأكد من الرجوع إلى الإصدار الصحيح من صورة Krane Docker في ملف النشر الخاص به. انظر سجل Krane Docker للحصول على العلامات المتاحة ، أو استخدم latest .
إذا جاءت مجموعة K8S الخاصة بك مع دعم وحدة التحكم المدمجة في كوبنيتس (تدعمها docker-desktop بشكل افتراضي) ، فيمكنك نشر Krane وتبعياتها مع أمر Docker Stack:
docker stack deploy
--orchestrator kubernetes
--namespace krane
--compose-file docker-compose.yml
--compose-file docker-compose.k8s.yml kraneملاحظة: تأكد من تعيين سياق Kube الحالي بشكل صحيح قبل تشغيل الأمر أعلاه!
يجب الآن نشر مكدس التطبيق في مجموعة Kubernetes وجميع الخدمات جاهزة ومعرضة. لاحظ أن Krane سيبدأ تلقائيًا لخادم حلقة التقارير وخادم Dashboard.
docker stack services --orchestrator kubernetes --namespace krane kraneسيؤدي الأمر أعلاه إلى إنتاج الإخراج التالي:
ID NAME MODE REPLICAS IMAGE PORTS
0de30651-dd5 krane_redisgraph replicated 1/1 redislabs/redisgraph:1.99.7 *:6379->6379/tcp
aa377a5f-62b krane_krane replicated 1/1 quay.io/appvia/krane:latest *:8000->8000/tcp
تحقق من وضع أمان Kubernetes الخاص بك RBAC عن طريق زيارة http: // localhost: 8000.
لاحظ أنه بالنسبة لعمليات نشر الكتلة عن بُعد ، من المحتمل أن تحتاج إلى خدمة Krane إلى الأمام أولاً
kubectl --context=my-remote-cluster --namespace=krane port-forward svc/krane 8000لحذف المكدس
docker stack rm krane
--orchestrator kubernetes
--namespace kraneسوف يعلمك Krane عن الحالات الشاذة المكتشفة من شدة متوسطة وعالية من خلال تكامل الركود.
لتمكين الإخطارات ، حدد Slack webhook_url & channel في ملف config/config.yaml ، أو بدلاً من ذلك تعيين متغيرات البيئة SLACK_WEBHOOK_URL و SLACK_CHANNEL . ستأخذ متغيرات البيئة الأسبقية على قيم ملفات التكوين.
يصف هذا القسم خطوات لتمكين التنمية المحلية.
تثبيت تبعيات رمز Krane مع
./bin/setup كرين يعتمد على redisgraph. docker-compose هي أسرع طريقة لجعل تبعيات Krane تعمل محليًا.
docker-compose up -d redisgraphلتفقد خدمة RedisGraph:
docker-compose psلوقف الخدمات:
docker-compose downفي هذه المرحلة ، يجب أن تكون قادرًا على تعديل نتائج الكود واختبار Krane من خلال استدعاء الأوامر في Shell المحلية.
$ ./bin/krane --help # to get help
$ ./bin/krane report -k docker-desktop # to generate your first report for
# local docker-desktop k8s cluster
...لتمكين وضع تطوير لوحة القيادة المحلية
$ cd dashboard
$ npm install
$ npm startسيؤدي ذلك تلقائيًا إلى بدء تشغيل خادم لوحة المعلومات وفتح متصفح افتراضي ومشاهدة تغييرات الملفات المصدر.
يأتي Krane مسبقًا لتجربة المطورين المحسنة مع Skaffold. إن التكرار في المشروع والتحقق من صحة التطبيق عن طريق تشغيل المكدس بالكامل في مجموعة Kubernetes المحلية أو البعيدة أصبح أسهل. يتيح RELOAL HOT CODE أن يتم نشر التغييرات المحلية تلقائيًا إلى الحاوية الجارية لدورة حياة التطوير بشكل أسرع.
skaffold dev --kube-context docker-desktop --namespace krane --port-forwardإجراء الاختبارات محليا مع
bundle exec rspecنرحب بأي مساهمات من المجتمع! إلقاء نظرة على دليل المساهمة لدينا لمزيد من المعلومات حول كيفية البدء. إذا كنت تستخدم krane ، أو ابحث عنها مفيدة ، أو كنت مهتمًا بشكل عام بأمان Kubernetes ، فيرجى إخبارنا من خلال بطولة ومشاهدة هذا الريبو. شكرًا!
انضم إلى مناقشة على قناة مجتمعنا.
Krane هو مشروع مجتمعي ونرحب بإسهاماتك. للإبلاغ عن خطأ ، اقترح تحسينًا ، أو طلب ميزة جديدة ، يرجى فتح مشكلة GitHub. ارجع إلى دليل المساهمة لدينا لمزيد من المعلومات حول كيفية المساعدة.
انظر خارطة الطريق الخاصة بنا للحصول على تفاصيل حول خططنا للمشروع.
المؤلف: Marcin Ciszak [email protected]
حقوق الطبع والنشر (C) 2019-2020 AppVia Ltd
يتم توزيع هذا المشروع بموجب ترخيص Apache ، الإصدار 2.0.