Konfiguracja Vault Hashicorp: Polityki, scieżki, silniki i inne – zarządzamy dostępem

Silniki

Silniki (secret engines) są komponentami, które odpowiadają za dużą część możliwośći Vault. Umożliwają one przechowywanie sekretów (Key/Value, Cubbyhole), generowanie jednorazowych kluczy (Nomad, AWS, PKI), szyfrowanie (SSH, Transit) i inne. Tymczasowo skupię się na jednym silniku – Kv, który umożliwia przechowywanie par Klucz/Wartość. W celu odblokowania silnika musimy wydać komendę:

$ vault secrets enable -path=newEngine kv

W rezultacie powinniśmy otrzymać:

$ vault secrets enable -path=newEngine kv
Success! Enabled the kv secrets engine at: newEngine/

Za flagą path podajemy ścieżkę do silnika, z którą wiąże się kilka rzeczy:

  • Każdy silnik mocowany jest w niezależnym miejscu.
  • Żaden silnik nie może być zamontowany w ścieżce innego silnika. Poprawne ścieżki dla dwóch silników to na przykład <punkt1;punkt2>, <punkt/p1;punkt/p2>, a złe to <punkt0;punkt0/p1>
  • Jest to abstrakcja bardzo podobno do linuksowego punktu mocowania i może udostępniać prawdziwe dyski, bazy danych, REST API i inne

Polityki

W ogólnym przypadku dostęp do zasobów możemy przypisywać bezpośrednio konkretnym użytkownikom i grupom. Niestety takie podejście szybko powoduje problemy. Z tego powodu powstały polityki, które opisują jak modyfikują domyślne uprawnienia. Modelowy przykład zarządzania politykami posiada następujące cechy:

  • Domyślnie polityki, grupy i użytkownicy nie mają żadnych uprawnień
  • Każda polityka nadpisuje pewną część uprawnień
  • Polityka dla dziecka nadpisuje politykę rodzica
  • Polityka nie powinna odbierać uprawnień
  • Nie przypisujemy uprawnień bezpośrednio do użytkowników i grup
  • Nie przypisujemy polityk bezpośrednio do użytkowników
  • Uprawnienia grupy to suma uprawnień nadanych przez przypisane jej polityki
  • Uprawnienia użytkownika to suma uprawnień grup do których jest przypisany

Te cechy ułatwiają zarządzanie i monitorowanie dostępu do zasobów.

Do podstawowych uprawnień jakie można nadać należą:

  • create – tworzenie nowego sekretu
  • read – odczyt istniejącego sekretu
  • update- aktualizacja istniejącego sekretu
  • delete – kasowanie sekretu
  • list – wypisanie istniejących sekretów

Polityki definiujemy w formacie HCl, który jest kompatybilny z jsonem:

path "secret/*" {
  capabilities = ["create"]
}

path "secret/foo" {
  capabilities = ["read"]
}

Taki plik z polityką możemy zapisać komendą:

vault policy write <nazwaPolityki> "<ścieżkaDoPlikuPolityki>"

Przykładowa konfiguracja

Teraz zobaczymy zrobić prototyp realnej implementacji. Najpierw dodamy silniki, które zorganizują nam strukturę:

  • develop – sekrety związane z rozwojem aplikacji. Każdy może robić co chce
  • prod – sekrety związane ze środowiskami klienckimi. Odczyt i aktualizacja dla devopsów, edycja dla “administracji”
  • licenses – Klucze aplikacji, itp. Odczyt dla wszystkich, edycja dla “administracji”

Wykonamy to następującymi komendami:

vault secrets enable -path="develop" kv
vault secrets enable -path="prod" kv
vault secrets enable -path="licenses" kv
vault secrets enable -path="users" kv

Teraz tworzymy pliki polityk:

path "develop/*" {
  capabilities = ["read", "list", "update", "create"]
}
path "prod/*" {
  capabilities = ["read", "list", "update"]
}
path "prod/*" {
  capabilities = ["read", "list", "update", "create", "delete"]
}
path "licenses /*" {
  capabilities = ["read", "list", "update", "create", "delete"]
}
path "licenses/*" {
  capabilities = ["read", "list"]
}

I same polityki:

vault policy write develop develop
vault policy write prodLite prodLite
vault policy write prodAdmin prodAdmin
vault policy write licensesFull  licensesFull 
vault policy write licensesAccess licensesAccess

Następnie powinniśmy stworzyć grupy, które będą implementowały te polityki. Ze względu na to, że do autoryzacji używamy LDAP użyjemy następujących komend:

vault write auth/ldap/groups/developers policies="develop,licensesAccess "
vault write auth/ldap/groups/devops policies="develop,prodLite"
vault write auth/ldap/groups/managers policies="develop,prodAdmin,licensesAdmin"

Teraz czas na crème de la crème – dodajemy użytkownika:

vault write auth/ldap/users/<username> groups=developers

Na koniec tworzymy zasoby:

vault write prod/db/login  value=prodDbLogin
vault write prod/db/password  value=prodDbPass
vault write develop/login  value=developDbLogin
vault write develop/password  value=developDbPass
vault write licenses/WinRar/key value="0033e049-8fea-461b-88fa-3a2d180b9c99"
vault write licenses/VisualStudio/key value="4f169e69-a6d1-46be-9f39-4c7960ce3d2c"

Uwierzytelniamy się w LDAP za pomocą komendy:

$ curl -s POST  http://127.0.0.1:8200/v1/auth/ldap/login/<naszLogin>  -d '{
"password":"<naszeHaslo>"
}' | json_pp

{
   "data" : {},
   "warnings" : [
      "no LDAP groups found in groupDN ''; only policies from locally-defined groups available"
   ],
   "lease_id" : "",
   "renewable" : false,
   "auth" : {
      "client_token" : "de8051b8-fdc1-4b5b-665b-8bb003ec07df",
      "metadata" : {
         "username" : "<naszLogin>"
      },
      "lease_duration" : 2764800,
      "entity_id" : "f7ccf9a5-a66f-4922-2137-ba95ba774125",
      "accessor" : "8278050c-1a62-f7ad-93e0-10c9e43707e5",
      "renewable" : true,
      "policies" : [
         "default",
         "developers"
      ]
   },
   "wrap_info" : null,
   "request_id" : "cb48256d-fd30-4416-0b16-7f8f6a8d5e3c",
   "lease_duration" : 0
}

Teraz używając client-token możemy odpytać się o pojedynczy zasób:

curl GET http://127.0.0.1:8200/v1/licenses/WinRar/key -sH 'X-Vault-Token: de8051b8-fdc1-4b5b-665b-8bb003ec07df' | json_pp
{
   "auth" : null,
   "wrap_info" : null,
   "data" : {
      "value" : "0033e049-8fea-461b-88fa-3a2d180b9c99"
   },
   "lease_duration" : 2764800,
   "lease_id" : "",
   "warnings" : null,
   "renewable" : false,
   "request_id" : "3dcec093-d6af-4386-861b-eece33e42d24"
}

Wylistować dostępne zasoby:

$ curl --request LIST http://127.0.0.1:8200/v1/licenses/ -sH 'X-Vault-Token: de8051b8-fdc1-4b5b-665b-8bb003ec07df'| json_pp
{
   "data" : {
      "keys" : [
         "VisualStudio/",
         "WinRar/"
      ]
   },
   "auth" : null,
   "lease_duration" : 0,
   "renewable" : false,
   "wrap_info" : null,
   "lease_id" : "",
   "request_id" : "5165f7cf-ff59-cd51-3712-44d42b16ba99",
   "warnings" : null
}

Ale tylko jeśli mamy uprawnienia:

$ curl --request LIST http://127.0.0.1:8200/v1/prod/ -sH 'X-Vault-Token: de8051b8-fdc1-4b5b-665b-8bb003ec07df' | json_pp
{
   "errors" : [
      "permission denied"
   ]
}

Możemy też tworzyć nowe zasoby:

$ curl --request POST --data '{"key": "super", "foo": "bar"}' -s -H 'X-Vault-Token: de8051b8-fdc1-4b5b-665b-8bb003ec07df' http://127.0.0.1:8200/v1/develop/brandNewSecret
$ curl --request LIST http://127.0.0.1:8200/v1/develop/ -sH 'X-Vault-Token: de8051b8-fdc1-4b5b-665b-8bb003ec07df'  | json_pp
{
   "lease_id" : "",
   "request_id" : "d820e2dd-9173-4211-74c0-61177198cc4c",
   "data" : {
      "keys" : [
         "brandNewSecret",
         "login",
         "password"
      ]
   },
   "lease_duration" : 0,
   "warnings" : null,
   "renewable" : false,
   "wrap_info" : null,
   "auth" : null
}

Oraz je usuwać:

$ curl --request DELETE http://127.0.0.1:8200/v1/develop/brandNewSecret -sH 'X-Vault-Token: de8051b8-fdc1-4b5b-665b-8bb003ec07df' 
$ curl --request LIST http://127.0.0.1:8200/v1/develop/ -sH 'X-Vault-Token: de8051b8-fdc1-4b5b-665b-8bb003ec07df'| json_pp
{
   "wrap_info" : null,
   "auth" : null,
   "data" : {
      "keys" : [
         "login",
         "password"
      ]
   },
   "request_id" : "99c0ce13-1b47-c182-6976-19aa69dea065",
   "warnings" : null,
   "lease_id" : "",
   "lease_duration" : 0,
   "renewable" : false
}

Podsumowanie

Nasz Vault jest praktycznie gotowy. Jesteśmy w stanie uwierzytelniać użytkowników i nadawać im dostępy. Jedyne co nam zostało to przygotowanie skryptu produkcyjnego i hardening.

Konfiguracja Vault Hashicorp: Uwierzytelnianie za pomocą LDAP

Konfiguracja LDAP

W zeszłym tygodniu zapoznaliśmy się z podstawami podstaw Vault Hashicorp. Teraz zajmiemy się konfiguracją LDAP. Jeśli nie miałeś nigdy głębszego kontaku z LDAP polecam na początek zytrax.
Pierwszą rzeczą jaką musimy znać jest konfiguracja naszego serwera, a dokładniej nazwa serwera (ldapUrl), ścieżka do użytkowników w domenie (tzw. Distinguish name, userDN), nazwa domeny (domain), unikalny identyfikator rekordu użytkownika (principalAttribute).

Żeby znaleźć informacje na własną rękę polecam ADExplorer z pakietu SysinternalsSuite (do pobrania tutaj).

ADExplorer – bardzo szybki start

Plik ADExplorer.exe zawiera cały program, uruchamiamy go dwuklikiem. Powinno wyświetlić się poniższe okno. Klikamy OK i czekamy na połączenie.

Po połączeniu z serwerem jesteśmy w stanie odczytać jego adres (zaznaczony na czerwono). W zielonym okręgu jest lupa, którą musimy kliknąć w celu wyszukiwania.

Po kliknięciu wyświetla się okno wyszukiwania.

  • Pole Attribute opisuje względem jakiej Właściwości przeszukujemy LDAP.
  • Value to Wartość jaką mają posiadać obiekty w danym atrybucie. Asterysk (*) zastępuje dowolny ciąg znaków
  • Add służy do dodania aktualnej pary Właściwość Wartość do zapytania
  • Remove usuwa aktualnie podświetloną parę w zapytaniu
  • Search wykonuje zapytanie
  • Każdy znaleziony wpis pojawia się w tabeli powyżej przycisku Search

Teraz czeka nas chwila zabawy, ponieważ musimy wyszukać nasze konto. Każde drzewo jest inne, ale jest kilka rzeczy które pomogą szybko znaleźć co potrzebujemy:

  • Spróbuj wyszukać w rozwijanej liście właściwości name, sn, surname, email, mail
  • Spróbuj przeszukać atrybuty zawierające słowo primary pod kątem swojej nazwy domenowej
  • Spróbuj wyszukać jakiegokolwiek użytkownika – przeglądając jego rekord łatwo zorientujesz się w strukturze rekordu użytkownika

Kiedy już znajdziemy swój wpis, ścieżka do naszego użytkownika pojawi się w zaznaczonym miejscu i będzie w formacie:

CN=&lt;Unikalny identyfikator&gt;,CN=A,CN=B,DC=EXAMPLE,DC=COM;

Po odrzuceniu pierwszej części “CN=identyfikator,”, zostaje nam userDN (CN=A,CN=B,DC=EXAMPLE,DC=COM).

Ostatnie co musimy znaleźć to unikalny identyfikator rekordu użytkownika. Będzie on zawierał w nazwie wyraz principal, a jego wartością będzie login@domena.

Właściwa konfiguracja LDAP

Napierw wyeksportujmy potrzebne wartości:

principalAttribute="principal"
upnDomain="example.com"
userDN="CN=A,CN=B,DC=EXAMPLE,DC=COM"
ldapUrl="ldap://example.com"

Do celów testowych LDAP może być skonfigurowany dwoma komendami:

vault auth enable ldap
vault write auth/ldap/config^
  url=$ldapUrl^
  userattr=$principalAttribute^
  insecure_tls=true^
  starttls=false^
  upndomain=$upnDomain^
  binddn=""^
  userdn=$userDN

Jeśli wszytko się udało, możemy się uwierzytelnić:

curl -s POST \
 http://127.0.0.1:8200/v1/auth/ldap/login/<naszLogin> \
 -d '{
"password":"<naszeHaslo>"
}'

Odpowiedź jest całkiem spora:

$ curl -s POST  http://127.0.0.1:8200/v1/auth/ldap/login/<naszLogin>  -d '{
"password":"<naszeHaslo>"
}' | json_pp

{
   "data" : {},
   "warnings" : [
      "no LDAP groups found in groupDN ''; only policies from locally-defined groups available"
   ],
   "lease_id" : "",
   "renewable" : false,
   "auth" : {
      "client_token" : "8be1e395-b1f0-be9f-69b5-db90205c1af2",
      "metadata" : {
         "username" : "<naszLogin>"
      },
      "lease_duration" : 2764800,
      "entity_id" : "f7ccf9a5-a66f-4922-2137-ba95ba774125",
      "accessor" : "8278050c-1a62-f7ad-93e0-10c9e43707e5",
      "renewable" : true,
      "policies" : [
         "default",
         "developers"
      ]
   },
   "wrap_info" : null,
   "request_id" : "cb48256d-fd30-4416-0b16-7f8f6a8d5e3c",
   "lease_duration" : 0
}

Nas interesuje tymczasowo kilka właściwości:

  • client_token – token którego będziemy używali do zapytań
  • policies – polityki jakim podlegamy. Powinieneś mieć je tymczasowo puste, a uzupełnimy je za tydzień
  • warnings – ponieważ nie skonfigurowaliśmy grup LDAP. Większość z nas nie będzie miała dostępu do jego konfiguracji, więc na razie się nie przejmujemy

Dodatkowo widnieje informacja, że nie znaleziono grup do których należy użytkownik. Programiści, zwykle nie mają dostępu do konfiguracji grup LDAP, ale problem można obejść przetrzymując grupy bezpośrednio w serwerze Vaulta.

W tym momencie mamy działające uwierzytelnienie z użyciem LDAP. Za tydzień pozanmy polityki, sposób ich użycia do autoryzacji oraz odczytamy pierwszy sekret jako zawykły użytkownik.

Wielka promocja ebooków – wybór – do 20 maja

Na ebookpoint jest fajna promocja. Poniżej wybór książek które wydają się ciekawe. Pogrubione to te, które moim zdaniem każdy programista powinien przeczytać.

Przetwarzanie równoległe i wielkoskalowe

Architektura, wzorce i dobre praktyki

JavaScript

 

Tajniki języka JavaScript – Kyle Simpson

Miękkie

Różności

Offtop

Konfiguracja Vault Hashicorp: Wstęp i uruchomienie wersji rozwojowej

Wstęp i uruchomienie

W pracy pojawił się problem współdzielenia kilku haseł pomiędzy programistami i ich aktualizacji. Najciekawszym rozwiązaniem tego problemu jest użycie programu Vault firmy HashiCorp. Do funkcji które zwróciły moją uwagę należą kontrola i logowanie dostępu, zarządzanie uprawnieniami, prosta integracja z LDAP oraz dostępne REST API.

Wszystkie kroki wykonywane są na systemie Windows10 z GitBash, jednak pomijając detale powinny działać na innych sytemach

W celu uruchomienia aplikacji należy pobrać paczkę (https://www.vaultproject.io/downloads.html)  i wypakować plik wykonywalny.

Dla wygody, przeniosłem go do program files:

mkdir C:\Program Files\HashiCorp
mkdir C:\Program Files\HashiCorp\Vault
mv vault.exe "C:\Program Files\HashiCorp"

Plik wykonywalny dodałem do ścieżki systemowej, dodatkowo warto dodać wartość VAULT_ADDR:

Żeby sprawdzić czy wszytko jest dobrze otwieramy nową linię komend i wpisujemy:

$ vault –h

Jeśli wszytko się udało powinniśmy uzyskać podstawowe informacje o składni:

$ vault -h
Usage: vault <command> [args]

Common commands:
    read        Read data and retrieves secrets
    write       Write data, configuration, and secrets
    delete      Delete secrets and configuration
    list        List data or secrets
    login       Authenticate locally
    server      Start a Vault server
    status      Print seal and HA status
    unwrap      Unwrap a wrapped secret

Other commands:
    audit          Interact with audit devices
    auth           Interact with auth methods
    lease          Interact with leases
    operator       Perform operator-specific tasks
    path-help      Retrieve API help for paths
    policy         Interact with policies
    secrets        Interact with secrets engines
    ssh            Initiate an SSH session
    token          Interact with tokens

Uruchomienie

Żeby uruchomić Vault w wersji developerskiej należy wydać polecenie:
Takiej konfiguracji nie należy używać na produkcji

$ vault server -dev -log-level=trace

Po jego wykonaniu powinny pojawić inforamcje o konfiguracji (z ważnych rzeczy root token i adres pod którym znajdziemy REST API) oraz logi

$ vault server -dev -log-level=trace
==> Vault server configuration:

                     Cgo: disabled
         Cluster Address: https://127.0.0.1:8201
              Listener 1: tcp (addr: "127.0.0.1:8200", cluster address: "127.0.0.1:8201", tls: "disabled")
               Log Level: trace
                   Mlock: supported: false, enabled: false
        Redirect Address: <strong>http://127.0.0.1:8200</strong>
                 Storage: inmem
                 Version: Vault v0.9.6
             Version Sha: 7e1fbde40afee241f81ef08700e7987d86fc7242

WARNING! dev mode is enabled! In this mode, Vault runs entirely in-memory
and starts unsealed with a single unseal key. The root token is already
authenticated to the CLI, so you can immediately begin using Vault.

You may need to set the following environment variable:

    $ set VAULT_ADDR=http://127.0.0.1:8200

The unseal key and root token are displayed below in case you want to
seal/unseal the Vault or re-authenticate.

Unseal Key: xSzRUrmkIB4SeiPSW1sdpMhO2XB4BhKybJwydW6H6Bk=
Root Token: <strong>3d6c5024-21f4-a464-1db6-6cf17fd7f76a</strong>

Development mode should NOT be used in production installations!

==> Vault server started! Log data will stream in below:


================= LOGI ==========================

Zapytania za pomocą REST API możemy wykonywać domyślnie pod adresem http://127.0.0.1:8200. Polecem do tego POSTMANA, jednak na potrzeby artykułu będę używał curla, ponieważ łatwiej jest prezentować komunikację po HTTP. W celu sprawdzenia czy działa silnik działa wykonujemy poniższe zapytanie:

$ curl -s http://127.0.0.1:8200/v1/sys/init

Jeśli wszytko jest dobrze powinniśmy otrzymać:

$ curl -s http://127.0.0.1:8200/v1/sys/init
{"initialized":true}

Root Token możemy znaleźć w informacjach wyświetlonych przez serwer po uruchomieniu (Uruchomienie serwera w konfiguracji deweloperskiej – wynik). Ponieważ będzie on nam potrzebny go do zmiennej:
Przed wykonaniem komendy podmień ciąg <Twój token> na własny root token.

export VAULT_TOKEN="<Twój token>"

W celu utworzenia sekretu wykonujemy nastepujące zapytanie:

curl \
-s \
--header "X-Vault-Token: $VAULT_TOKEN" \
--request POST \
--data '{"naszKlucz": "naszawartosc"}' \
http://127.0.0.1:8200/v1/secret/hello

W celu odczytania możemy użyć nastepującej komendy:

curl \
-s \
--header "X-Vault-Token: $VAULT_TOKEN" \
http://127.0.0.1:8200/v1/secret/hello

Co zwróci nam pary klucz-wartość umieszczone w sekrecie oraz kilka dodatkowych informacji:

$ curl -s --header "X-Vault-Token: $VAULT_TOKEN" http://127.0.0.1:8200/v1/secret/hello
{"request_id":"2cce8a90-ffa5-b257-bcbe-fb72368cf706","lease_id":"","renewable":false,"lease_duration":2764800,"data":{"naszKlucz":"naszawartosc"},"wrap_info":null,"warnings":null,"auth":null}

W tym momencie jesteśmy w stanie współdzielić rózne sekrety, jednak są one praktycznie niezabezpieczone, ponieważ musimy udostępnić token roota który posiada najwyższe uprawnienia w aplikacji. W następnym wpise skonfiguruję autoryzację za pomocą LDAP.

Osobne connectionString dla każdego użytkownika

Wstęp

Jedna baza na programistę jest ogólnie akceptowanym standardem. Niestety nie zawsze możemy doprowadzić do sytuacji, gdzie jedna definicja połączeń do instancji będzie odpowiadała każdemu programiście.

Rozwiązaniem tego problemu jest posiadanie przez każdego użytkownika własnego zestawu definicji połączeń.

Wydzielenie folderu na definicje

  1. Tworzymy folder ConnectionConfigurations
  2. Tworzymy plik: ConnectionConfigurations/ConnectionConfiguration.config
  3. Wycinamy nasze definicje połączeń z Web.config i wklejamy je po drugiej linii nowego pliku
  4. Po tych operacjach nasz plik powinien wyglądać następująco:
    <connectionStrings>
       <clear/>
       <!-- Tu wklejamy definicje -->
       <add name="ConnName" connectionString="ConnStr" />
    </connectionStrings>
    
  5. Teraz w Web.config dodajemy linijke 3:
    <?xml version="1.0" encoding="utf-8"?>
    <configuration>
      <connectionStrings configSource="ConnectionConfigurations/ConnectionConfiguration.config" />
    </configuration>
    
  6. Na tym etapie mamy już wydzielone definicje połączeń do osobnego pliku. Teraz powinniśmy sprawdzić, czy nasza aplikacja się buduje. Jeśli tak przechodzimy do następnej sekcji.

Konfiguracja domyślna

  1. Na początek zmieniamy nazwę ConnectionConfiguration.config na ConnectionConfiguration.default.config
  2. Następnie otwieramy właściwości naszego projektu z plikiem Web.config i przechodzimy do zakładki Build Events
  3. W oknie zatytułowanym Pre-build event command line wprowadzamy następującą komendę:
    if exist $(ProjectDir)ConnectionConfigurations\ConnectionConfiguration.default.config (echo f|xcopy /Y $(ProjectDir)ConnectionConfigurations\ConnectionConfiguration.default.config $(ProjectDir)ConnectionConfigurations\ConnectionConfiguration.config)
    

    Linijka służy do skopiowani domyślnej konfiguracji:

    • Warunek if sprawdza czy plik istnieje, i jeśli tak, wykonywana jest komenda kopiująca
    • Xcopy odpowiada za skopianie pliku ze ścieżki źródłowej do docelowej
    • Parametr f, przekazany przez echo, informuje że kopiowany będzie plik
    • Flaga /Y wyłącza pytanie o nadpisanie pliku
    • $(ProjectDir) to makro zwracające ścieżkę głównego folderu aktualnego projektu
  4. Teraz sprawdzamy czy projekt się buduje. Jeśli tak, a projekt po uruchomieniu działa poprawnie przechodzimy dalej.

Osobna konfiguracja dla każdego użytkownika

  1. Tworzymy nowy plik ConnectionConfigurations\ConnectionConfiguration..config, np.: ConnectionConfigurations\ConnectionConfiguration.rafal.config. Powinien on wyglądać jak onnectionConfiguration.default.config, ale zawierać dogodne dla nas definicje połączeń
  2. Nastepnie w Pre-build event command line dodajemy po naszym wpisie kolejną linijkę o następującej treści:

    if  $(ConfigurationName) == Debug (if exist $(ProjectDir)ConnectionConfigurations\ConnectionConfiguration.$(username).config (echo f|xcopy /Y $(ProjectDir)ConnectionConfigurations\ConnectionConfiguration.$(username).config $(ProjectDir)ConnectionConfigurations\ConnectionConfiguration.config))
    
  3. W tym wypadku mamy zagnieżdżonego ifa – niestety nie udało mi się wymusić operacji oraz.

    • Pierwszy if sprawdza czy jesteśmy w Debug – szybkie obejście, żeby nie publikować własnych połączeń na serwery
    • Makro $(username) pobiera aktualną nazwę użytkownika.
    • Komendy wykonują się kolejno, więc jeśli jakiś użytkownik ma swój plik, jego zmiany nadpiszą konfigurację domyślną
  4. Sprawdź czy działa – jeśli tak jesteśmy w domu

Na koniec dodam, że główną inspiracją był ten post.

Z TFS do GIT w 10 minut

Każdy kto spróbował pracy z git flow nie ma ochoty wracać do odpowiednika z Microsoftu. Jednak w wypadku starszych projektów szkoda stracić historię zmian. Poniżej szybki przewodnik jak to zrobić.

Pobieramy GitTf:

A następnie rozpakowujemy go do wybranego folderu.

Tworzymy lokalne repozytorium:

  1. Otwieramy systemową linię komend i przechodzimy do folderu z plikiem git-tf.cmd
  2. Komenda do sklonowania repozytorium wygląda następująco:
    git-tf clone <span style="color: #ff0000;"><NazwaKolekcji></span> "$/<span style="color: #339966;"><NazwaBrancha></span>" --deep
    

    Obie  nazwy można wyciągnąć z linku do TFS: <nazwa_serwera>/ToolsCollection/<NazwaProjektu>

Idziemy na kawę*

Zakończenie

  1. Wchodzimy do folderu <NazwaProjektu>
  2. Otwieramy gitBash
  3. Pozytywnie puszujemy na serwer

Podpatrzone stąd
*Nie wliczamy do tytułowych 10 min

Zagwozdka z LINQ to SQL

Cześć,

ostatnio spędziłem dłuższy czas głowiąc się nad tym, dlaczego pętla w linii 23 nie aktualizuje wartości na liście:

public List<VM> Foo(IDomainContext ctx)
{
   var result = new List<VM>();
   foreach(var oneOfReportedMonths in monthsToReport)
   {
      var reports =
      _ctx.Items<reports>()
         .Where(x => IsConditionPassed(x,oneOfReportedMonths))
         .GroupBy(x => x.Id)
         .Select(x => new
         {
            Id = x.Key,
            Value = x.Sum(y => y.SubValue)
         }).ToList();
   
      var otherReports = 
      _ctx.Items<OtherReport>()
         .Select(x => new VM()
         {
            Id = x.Id,
         });

      foreach (var item in otherReports)
      {
         var P1 = reports
             .FirstOrDefault(x => x.Id == item.Id)?.Value ?? 0;
      }
      result.AddRange(otherReports);
   }
   return result;
}
Rozwiązanie było banalne:
Do poczytania:

Prawie integracja Visual Studio z Git Bash w 3 min

Cześć,

Z pewnością wielu z was używa GIT i Visual Studio. Niezależnie od integracji dostarczanej od MS, musimy czasem użyć GIT Bash (albo robimy to regularnie 🙂 ). Poniżej przyjemny sposób na szybkie otwieranie konsoli w katalogu solucji.

  1. Z menu Tools wybieramy opcję External Tools
  2. Wciskamy przycisk Add
  3. Uzupełniamy dostępne pola:
    1. Title – Wybrany przez nas tytuł. Będzie widoczny w menu
    2. Command – podajemy ścieżkę do aplikacji git-bash.exe
    3. Arguments – Pozostawiamy puste
    4. Initial directory – Z menu po prawo wybieramy Solution Directory
  4. Wciskamy Ok
  5. Od teraz możemy otwierać git-bash wybierając tytuł komendy z menu Tools

Oczywiście najwygodniejszą opcją będzie przypięcie narzędzia do skrótu klawiaturowego lub własnego paska narzędziowego (Add Commands…/Tools/External Tool <numer zależny od pozycji w menu>).

Rozwiązanie nie jest idealne i w najbliższym czasie będę szukał integracji w formie konsoli dostępnej jako ono wewnątrz VS.

Sztuczka podpatrzona jest stąd.

Pozdrawiam
Rafał

 

Co zrobić gdy pracowaliśmy na złej gałęzi

TL;DR - kliknij aby rozwinąć

 

Cześć,

bardzo się cieszę, że odwiedziłeś mój blog. Chiałbym Ci opowiedzieć o problemie jaki miałem w pracy, oraz o tym jak go rozwiązałem.

Otóż,po paru godzinach zorientowałem się, że zmiany wprowadzałem na złej gałęzi. Niestety, kiedy próbowałem zmienić ją na właściwą otrzymałem znane wielu osobom ostrzeżenie:

~/Documents/GitTest (master)
$ git checkout dev
error: Your local changes to the following files would be overwritten by checkout:
 index.html
Please commit your changes or stash them before you switch branches.
Aborting

Jego powodem jest to, że gałęzie aktualna i ta na którą próbujemy się przełączyć są niezgodne, a zmiana jednej na drugą mogła by spowodować problemy.  Żeby pokazać Ci dlaczego tak się dzieje i jak to naprawić stworzyłem syntetyczny przypadek:

Na początek tworzymy prosty plik html:

<html>
<head></head>
<body>
Wersja pierwsza
</body>
</html>

oraz nowe repozytorium GIT z jednym commitem:

 ~/Documents/GitStash (master)
$ git init
Initialized empty Git repository in C:/Users/user/Documents/GitStash/.git/

 ~/Documents/GitStash (master)
$ git add *

 ~/Documents/GitStash (master)
$ git commit -m firstCommit
[master (root-commit) 8b7de5c] firstCommit
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 index.html

Teraz zrobimy sobie drugi branch, na którym będziemy rozwijali naszą stronę:

 ~/Documents/GitStash (master)
$ git branch develop
 ~/Documents/GitStash (master)
$ git checkout develop
 ~/Documents/GitStash (develop)

Kolejnym krokiem jest zmiana pliku:

<html>
<head></head>
<body>
Wersja z gałęzi test
</body>
</html>

Teraz robimy commit i wracamy na gałąź master:

 ~/Documents/GitStash (develop)
$ git commit -a -m testVersion
[test 47e6549] secondVersionOfPage
 1 file changed, 7 insertions(+)
 ~/Documents/GitStash (develop)
$ git checkout master
 ~/Documents/GitStash (develop)

Znowu zmieniamy index.html:


<html>
<head></head>
<body>
Wersja z którą chcemy mieć w develop
</body>
</html>

Teraz jeśli spróbujemy zmienić aktualna gałąź dostaniemy błąd podobny do tego z początku postu:

$ git checkout develop
error: Your local changes to the following files would be overwritten by checkout:
        index.html
Please commit your changes or stash them before you switch branches.
Aborting

Na szczęście GIT daje nam dwa wyjścia. Pierwsze to git commit, a drugie to git stash.

Komenda git commit odpada, niestety potrzebujemy tych zmian w innej gałezi. Natomiast git stash może nam pomóc.

Polecenie git stash umieszcza aktualne zmiany w schowku. Jak działa pokażę Ci poniżej. Najpierw jednak zajrzyjmy jakie mamy zamiany w kodzie:

$ git status
On branch master
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

        modified:   index.html

no changes added to commit (use "git add" and/or "git commit -a")

Jak widzimy jest jeden zmieniony plik. Skoro wiemy co się dzieje przejdziemy do rozwiązania problemu. W tym celu użyjemy polecenia git stash:

$ git stash
Saved working directory and index state WIP on master: 0863480 InitialCommit
HEAD is now at 0863480 InitialCommit
$ git status
On branch master
nothing to commit, working tree clean

Jak widzimy z komendy status, nie ma żadnych zmian. To samo potwierdza szybki rzut oka na kod:

<html>
<head></head>
<body>
Wersja pierwsza
</body>
</html>

Gdzie w takim razie podziały się nasze zmiany? Są w schowku! Żeby pokazać co w nim jest użyjemy komendy git stash list:

$ git stash list
stash@{0}: WIP on master: 0863480 InitialCommit

Jak widzimy jest tam jedna zmiana, którą możemy odzyskać w każdej chwili. Na razie jednak jest nam to niepotrzebne, ponieważ chcemy przejść do gałęzi develop:

$ git checkout develop
Switched to branch 'develop'

Udaje się to bez problemów. Pytaniem jest, jak możemy teraz odzyskać dane? Używamy komendy git stash apply, która użyje ostatnich zmian odłożonych do schowka:

$ git stash apply
Auto-merging index.html
CONFLICT (content): Merge conflict in index.html

Teraz wystarczy spojrzeć do kodu,

<html>
<head></head>
<body>
<<<<<<< Updated upstream Wersja z gałęzi develop ======= Wersja z która powinna być w gałęzi develop >>>>>>> Stashed changes
</body>
</html>

scalić wersje,

<html>
<head></head>
<body>
Wersja z która powinna być  w gałęzi  develop
</body>
</html>

i zrobić commit.

$ git commit -a -m secondDevelopCommit
[develop 74a8680] secondDevelopCommit
 1 file changed, 4 insertions(+)

Na koniec sprzątamy. W tym celu usuniemy zestaw zmian ostatnio dodany do schowka:

$ git stash list
stash@{0}: WIP on master: 9b7d3dc initialCommit

$ git stash drop
Dropped refs/stash@{0} (f7783d7bea969d85e294823d593535b79f89c207)

$ git stash list

Jak widzisz po poleceniu git stash drop usunięty został ostatnio dodany zestaw zmian.

W ten sposób szybko i bezboleśnie możesz przerzucić niezacommitowane zmiany ze złego brancha do dobrego.

Mam nadzieję, że moja notka będzie dla Ciebie przydatna. W normalnych warunkach nie powinieneś mieć potrzeby jej użyć, ale nie ma błędu którego nie da się popełnić.

Pozdrawiam
Rafał Karwat